# Copyright (c) 2010 WorkWare Systems http://www.workware.net.au/ # All rights reserved # @synopsis: # # The 'cc' module supports checking various 'features' of the C or C++ # compiler/linker environment. Common commands are 'cc-check-includes', # 'cc-check-types', 'cc-check-functions', 'cc-with' and 'make-config-header' # # The following environment variables are used if set: # ## CC - C compiler ## CXX - C++ compiler ## CPP - C preprocessor ## CCACHE - Set to "none" to disable automatic use of ccache ## CPPFLAGS - Additional C preprocessor compiler flags (C and C++), before CFLAGS, CXXFLAGS ## CFLAGS - Additional C compiler flags ## CXXFLAGS - Additional C++ compiler flags ## LDFLAGS - Additional compiler flags during linking ## LINKFLAGS - ?How is this different from LDFLAGS? ## LIBS - Additional libraries to use (for all tests) ## CROSS - Tool prefix for cross compilation # # The following variables are defined from the corresponding # environment variables if set. # ## CC_FOR_BUILD ## LD use system options {} # Checks for the existence of the given function by linking # proc cctest_function {function} { cctest -link 1 -declare "extern void $function\(void);" -code "$function\();" } # Checks for the existence of the given type by compiling proc cctest_type {type} { cctest -code "$type _x;" } # Checks for the existence of the given type/structure member. # e.g. "struct stat.st_mtime" proc cctest_member {struct_member} { # split at the first dot regexp {^([^.]+)[.](.*)$} $struct_member -> struct member cctest -code "static $struct _s; return sizeof(_s.$member);" } # Checks for the existence of the given define by compiling # proc cctest_define {name} { cctest -code "#ifndef $name\n#error not defined\n#endif" } # Checks for the existence of the given name either as # a macro (#define) or an rvalue (such as an enum) # proc cctest_decl {name} { cctest -code "#ifndef $name\n(void)$name;\n#endif" } # @cc-check-sizeof type ... # # Checks the size of the given types (between 1 and 32, inclusive). # Defines a variable with the size determined, or 'unknown' otherwise. # e.g. for type 'long long', defines 'SIZEOF_LONG_LONG'. # Returns the size of the last type. # proc cc-check-sizeof {args} { foreach type $args { msg-checking "Checking for sizeof $type..." set size unknown # Try the most common sizes first foreach i {4 8 1 2 16 32} { if {[cctest -code "static int _x\[sizeof($type) == $i ? 1 : -1\] = { 1 };"]} { set size $i break } } msg-result $size set define [feature-define-name $type SIZEOF_] define $define $size } # Return the last result get-define $define } # Checks for each feature in $list by using the given script. # # When the script is evaluated, $each is set to the feature # being checked, and $extra is set to any additional cctest args. # # Returns 1 if all features were found, or 0 otherwise. proc cc-check-some-feature {list script} { set ret 1 foreach each $list { if {![check-feature $each $script]} { set ret 0 } } return $ret } # @cc-check-includes includes ... # # Checks that the given include files can be used. proc cc-check-includes {args} { cc-check-some-feature $args { set with {} if {[dict exists $::autosetup(cc-include-deps) $each]} { set deps [dict keys [dict get $::autosetup(cc-include-deps) $each]] msg-quiet cc-check-includes {*}$deps foreach i $deps { if {[have-feature $i]} { lappend with $i } } } if {[llength $with]} { cc-with [list -includes $with] { cctest -includes $each } } else { cctest -includes $each } } } # @cc-include-needs include required ... # # Ensures that when checking for '$include', a check is first # made for each '$required' file, and if found, it is included with '#include'. proc cc-include-needs {file args} { foreach depfile $args { dict set ::autosetup(cc-include-deps) $file $depfile 1 } } # @cc-check-types type ... # # Checks that the types exist. proc cc-check-types {args} { cc-check-some-feature $args { cctest_type $each } } # @cc-check-defines define ... # # Checks that the given preprocessor symbols are defined. proc cc-check-defines {args} { cc-check-some-feature $args { cctest_define $each } } # @cc-check-decls name ... # # Checks that each given name is either a preprocessor symbol or rvalue # such as an enum. Note that the define used is 'HAVE_DECL_xxx' # rather than 'HAVE_xxx'. proc cc-check-decls {args} { set ret 1 foreach name $args { msg-checking "Checking for $name..." set r [cctest_decl $name] define-feature "decl $name" $r if {$r} { msg-result "ok" } else { msg-result "not found" set ret 0 } } return $ret } # @cc-check-functions function ... # # Checks that the given functions exist (can be linked). proc cc-check-functions {args} { cc-check-some-feature $args { cctest_function $each } } # @cc-check-members type.member ... # # Checks that the given type/structure members exist. # A structure member is of the form 'struct stat.st_mtime'. proc cc-check-members {args} { cc-check-some-feature $args { cctest_member $each } } # @cc-check-function-in-lib function libs ?otherlibs? # # Checks that the given function can be found in one of the libs. # # First checks for no library required, then checks each of the libraries # in turn. # # If the function is found, the feature is defined and 'lib_$function' is defined # to '-l$lib' where the function was found, or "" if no library required. # In addition, '-l$lib' is prepended to the 'LIBS' define. # # If additional libraries may be needed for linking, they should be specified # with '$extralibs' as '-lotherlib1 -lotherlib2'. # These libraries are not automatically added to 'LIBS'. # # Returns 1 if found or 0 if not. # proc cc-check-function-in-lib {function libs {otherlibs {}}} { msg-checking "Checking libs for $function..." set found 0 cc-with [list -libs $otherlibs] { if {[cctest_function $function]} { msg-result "none needed" define lib_$function "" incr found } else { foreach lib $libs { cc-with [list -libs -l$lib] { if {[cctest_function $function]} { msg-result -l$lib define lib_$function -l$lib # prepend to LIBS define LIBS "-l$lib [get-define LIBS]" incr found break } } } } } define-feature $function $found if {!$found} { msg-result "no" } return $found } # @cc-check-tools tool ... # # Checks for existence of the given compiler tools, taking # into account any cross compilation prefix. # # For example, when checking for 'ar', first 'AR' is checked on the command # line and then in the environment. If not found, '${host}-ar' or # simply 'ar' is assumed depending upon whether cross compiling. # The path is searched for this executable, and if found 'AR' is defined # to the executable name. # Note that even when cross compiling, the simple 'ar' is used as a fallback, # but a warning is generated. This is necessary for some toolchains. # # It is an error if the executable is not found. # proc cc-check-tools {args} { foreach tool $args { set TOOL [string toupper $tool] set exe [get-env $TOOL [get-define cross]$tool] if {[find-executable $exe]} { define $TOOL $exe continue } if {[find-executable $tool]} { msg-result "Warning: Failed to find $exe, falling back to $tool which may be incorrect" define $TOOL $tool continue } user-error "Failed to find $exe" } } # @cc-check-progs prog ... # # Checks for existence of the given executables on the path. # # For example, when checking for 'grep', the path is searched for # the executable, 'grep', and if found 'GREP' is defined as 'grep'. # # If the executable is not found, the variable is defined as 'false'. # Returns 1 if all programs were found, or 0 otherwise. # proc cc-check-progs {args} { set failed 0 foreach prog $args { set PROG [string toupper $prog] msg-checking "Checking for $prog..." if {![find-executable $prog]} { msg-result no define $PROG false incr failed } else { msg-result ok define $PROG $prog } } expr {!$failed} } # @cc-path-progs prog ... # # Like cc-check-progs, but sets the define to the full path rather # than just the program name. # proc cc-path-progs {args} { set failed 0 foreach prog $args { set PROG [string toupper $prog] msg-checking "Checking for $prog..." set path [find-executable-path $prog] if {$path eq ""} { msg-result no define $PROG false incr failed } else { msg-result $path define $PROG $path } } expr {!$failed} } # Adds the given settings to $::autosetup(ccsettings) and # returns the old settings. # proc cc-add-settings {settings} { if {[llength $settings] % 2} { autosetup-error "settings list is missing a value: $settings" } set prev [cc-get-settings] # workaround a bug in some versions of jimsh by forcing # conversion of $prev to a list llength $prev array set new $prev foreach {name value} $settings { switch -exact -- $name { -cflags - -includes { # These are given as lists lappend new($name) {*}[list-non-empty $value] } -declare { lappend new($name) $value } -libs { # Note that new libraries are added before previous libraries set new($name) [list {*}[list-non-empty $value] {*}$new($name)] } -link - -lang - -nooutput { set new($name) $value } -source - -sourcefile - -code { # XXX: These probably are only valid directly from cctest set new($name) $value } default { autosetup-error "unknown cctest setting: $name" } } } cc-store-settings [array get new] return $prev } proc cc-store-settings {new} { set ::autosetup(ccsettings) $new } proc cc-get-settings {} { return $::autosetup(ccsettings) } # Similar to cc-add-settings, but each given setting # simply replaces the existing value. # # Returns the previous settings proc cc-update-settings {args} { set prev [cc-get-settings] cc-store-settings [dict merge $prev $args] return $prev } # @cc-with settings ?{ script }? # # Sets the given 'cctest' settings and then runs the tests in '$script'. # Note that settings such as '-lang' replace the current setting, while # those such as '-includes' are appended to the existing setting. # # If no script is given, the settings become the default for the remainder # of the 'auto.def' file. # ## cc-with {-lang c++} { ## # This will check with the C++ compiler ## cc-check-types bool ## cc-with {-includes signal.h} { ## # This will check with the C++ compiler, signal.h and any existing includes. ## ... ## } ## # back to just the C++ compiler ## } # # The '-libs' setting is special in that newer values are added *before* earlier ones. # ## cc-with {-libs {-lc -lm}} { ## cc-with {-libs -ldl} { ## cctest -libs -lsocket ... ## # libs will be in this order: -lsocket -ldl -lc -lm ## } ## } # # If you wish to invoke something like cc-check-flags but not have -cflags updated, # use the following idiom: # ## cc-with {} { ## cc-check-flags ... ## } proc cc-with {settings args} { if {[llength $args] == 0} { cc-add-settings $settings } elseif {[llength $args] > 1} { autosetup-error "usage: cc-with settings ?script?" } else { set save [cc-add-settings $settings] set rc [catch {uplevel 1 [lindex $args 0]} result info] cc-store-settings $save if {$rc != 0} { return -code [dict get $info -code] $result } return $result } } # @cctest ?settings? # # Low level C/C++ compiler checker. Compiles and or links a small C program # according to the arguments and returns 1 if OK, or 0 if not. # # Supported settings are: # ## -cflags cflags A list of flags to pass to the compiler ## -includes list A list of includes, e.g. {stdlib.h stdio.h} ## -declare code Code to declare before main() ## -link 1 Don't just compile, link too ## -lang c|c++ Use the C (default) or C++ compiler ## -libs liblist List of libraries to link, e.g. {-ldl -lm} ## -code code Code to compile in the body of main() ## -source code Compile a complete program. Ignore -includes, -declare and -code ## -sourcefile file Shorthand for -source [readfile [get-define srcdir]/$file] ## -nooutput 1 Treat any compiler output (e.g. a warning) as an error # # Unless '-source' or '-sourcefile' is specified, the C program looks like: # ## #include /* same for remaining includes in the list */ ## declare-code /* any code in -declare, verbatim */ ## int main(void) { ## code /* any code in -code, verbatim */ ## return 0; ## } # # And the command line looks like: # ## CC -cflags CFLAGS CPPFLAGS conftest.c -o conftest.o ## CXX -cflags CXXFLAGS CPPFLAGS conftest.cpp -o conftest.o # # And if linking: # ## CC LDFLAGS -cflags CFLAGS conftest.c -o conftest -libs LIBS ## CXX LDFLAGS -cflags CXXFLAGS conftest.c -o conftest -libs LIBS # # Any failures are recorded in 'config.log' # proc cctest {args} { set tmp conftest__ # Easiest way to merge in the settings cc-with $args { array set opts [cc-get-settings] } if {[info exists opts(-sourcefile)]} { set opts(-source) [readfile [get-define srcdir]/$opts(-sourcefile) "#error can't find $opts(-sourcefile)"] } if {[info exists opts(-source)]} { set lines $opts(-source) } else { foreach i $opts(-includes) { if {$opts(-code) ne "" && ![feature-checked $i]} { # Compiling real code with an unchecked header file # Quickly (and silently) check for it now # Remove all -includes from settings before checking set saveopts [cc-update-settings -includes {}] msg-quiet cc-check-includes $i cc-store-settings $saveopts } if {$opts(-code) eq "" || [have-feature $i]} { lappend source "#include <$i>" } } lappend source {*}$opts(-declare) lappend source "int main(void) {" lappend source $opts(-code) lappend source "return 0;" lappend source "}" set lines [join $source \n] } # Build the command line set cmdline {} lappend cmdline {*}[get-define CCACHE] switch -exact -- $opts(-lang) { c++ { set src conftest__.cpp lappend cmdline {*}[get-define CXX] set cflags [get-define CXXFLAGS] } c { set src conftest__.c lappend cmdline {*}[get-define CC] set cflags [get-define CFLAGS] } default { autosetup-error "cctest called with unknown language: $opts(-lang)" } } if {$opts(-link)} { lappend cmdline {*}[get-define LDFLAGS] } else { lappend cflags {*}[get-define CPPFLAGS] set tmp conftest__.o lappend cmdline -c } lappend cmdline {*}$opts(-cflags) {*}[get-define cc-default-debug ""] {*}$cflags lappend cmdline $src -o $tmp if {$opts(-link)} { lappend cmdline {*}$opts(-libs) {*}[get-define LIBS] } # At this point we have the complete command line and the # complete source to be compiled. Get the result from cache if # we can if {[info exists ::cc_cache($cmdline,$lines)]} { msg-checking "(cached) " set ok $::cc_cache($cmdline,$lines) if {$::autosetup(debug)} { configlog "From cache (ok=$ok): [join $cmdline]" configlog "============" configlog $lines configlog "============" } return $ok } writefile $src $lines\n set ok 1 set err [catch {exec-with-stderr {*}$cmdline} result errinfo] if {$err || ($opts(-nooutput) && [string length $result])} { configlog "Failed: [join $cmdline]" configlog $result configlog "============" configlog "The failed code was:" configlog $lines configlog "============" set ok 0 } elseif {$::autosetup(debug)} { configlog "Compiled OK: [join $cmdline]" configlog "============" configlog $lines configlog "============" } file delete $src file delete $tmp # cache it set ::cc_cache($cmdline,$lines) $ok return $ok } # @make-autoconf-h outfile ?auto-patterns=HAVE_*? ?bare-patterns=SIZEOF_*? # # Deprecated - see 'make-config-header' proc make-autoconf-h {file {autopatterns {HAVE_*}} {barepatterns {SIZEOF_* HAVE_DECL_*}}} { user-notice "*** make-autoconf-h is deprecated -- use make-config-header instead" make-config-header $file -auto $autopatterns -bare $barepatterns } # @make-config-header outfile ?-auto patternlist? ?-bare patternlist? ?-none patternlist? ?-str patternlist? ... # # Examines all defined variables which match the given patterns # and writes an include file, '$file', which defines each of these. # Variables which match '-auto' are output as follows: # - defines which have the value '0' are ignored. # - defines which have integer values are defined as the integer value. # - any other value is defined as a string, e.g. '"value"' # Variables which match '-bare' are defined as-is. # Variables which match '-str' are defined as a string, e.g. '"value"' # Variables which match '-none' are omitted. # # Note that order is important. The first pattern that matches is selected. # Default behaviour is: # ## -bare {SIZEOF_* HAVE_DECL_*} -auto HAVE_* -none * # # If the file would be unchanged, it is not written. proc make-config-header {file args} { set guard _[string toupper [regsub -all {[^a-zA-Z0-9]} [file tail $file] _]] file mkdir [file dirname $file] set lines {} lappend lines "#ifndef $guard" lappend lines "#define $guard" # Add some defaults lappend args -bare {SIZEOF_* HAVE_DECL_*} -auto HAVE_* foreach n [lsort [dict keys [all-defines]]] { set value [get-define $n] set type [calc-define-output-type $n $args] switch -exact -- $type { -bare { # Just output the value unchanged } -none { continue } -str { set value \"[string map [list \\ \\\\ \" \\\"] $value]\" } -auto { # Automatically determine the type if {$value eq "0"} { lappend lines "/* #undef $n */" continue } if {![string is integer -strict $value]} { set value \"[string map [list \\ \\\\ \" \\\"] $value]\" } } "" { continue } default { autosetup-error "Unknown type in make-config-header: $type" } } lappend lines "#define $n $value" } lappend lines "#endif" set buf [join $lines \n] write-if-changed $file $buf { msg-result "Created $file" } } proc calc-define-output-type {name spec} { foreach {type patterns} $spec { foreach pattern $patterns { if {[string match $pattern $name]} { return $type } } } return "" } # Initialise some values from the environment or commandline or default settings foreach i {LDFLAGS LIBS CPPFLAGS LINKFLAGS CFLAGS} { lassign $i var default define $var [get-env $var $default] } if {[env-is-set CC]} { # Set by the user, so don't try anything else set try [list [get-env CC ""]] } else { # Try some reasonable options set try [list [get-define cross]cc [get-define cross]gcc] } define CC [find-an-executable {*}$try] if {[get-define CC] eq ""} { user-error "Could not find a C compiler. Tried: [join $try ", "]" } define CPP [get-env CPP "[get-define CC] -E"] # XXX: Could avoid looking for a C++ compiler until requested # If CXX isn't found, it is set to the empty string. if {[env-is-set CXX]} { define CXX [find-an-executable -required [get-env CXX ""]] } else { define CXX [find-an-executable [get-define cross]c++ [get-define cross]g++] } # CXXFLAGS default to CFLAGS if not specified define CXXFLAGS [get-env CXXFLAGS [get-define CFLAGS]] # May need a CC_FOR_BUILD, so look for one define CC_FOR_BUILD [find-an-executable [get-env CC_FOR_BUILD ""] cc gcc false] if {[get-define CC] eq ""} { user-error "Could not find a C compiler. Tried: [join $try ", "]" } # These start empty and never come from the user or environment define AS_CFLAGS "" define AS_CPPFLAGS "" define AS_CXXFLAGS "" define CCACHE [find-an-executable [get-env CCACHE ccache]] # If any of these are set in the environment, propagate them to the AUTOREMAKE commandline foreach i {CC CXX CCACHE CPP CFLAGS CXXFLAGS CXXFLAGS LDFLAGS LIBS CROSS CPPFLAGS LINKFLAGS CC_FOR_BUILD LD} { if {[env-is-set $i]} { # Note: If the variable is set on the command line, get-env will return that value # so the command line will continue to override the environment define-append-argv AUTOREMAKE $i=[get-env $i ""] } } # Initial cctest settings cc-store-settings {-cflags {} -includes {} -declare {} -link 0 -lang c -libs {} -code {} -nooutput 0} set autosetup(cc-include-deps) {} msg-result "C compiler...[get-define CCACHE] [get-define CC] [get-define CFLAGS] [get-define CPPFLAGS]" if {[get-define CXX] ne "false"} { msg-result "C++ compiler...[get-define CCACHE] [get-define CXX] [get-define CXXFLAGS] [get-define CPPFLAGS]" } msg-result "Build C compiler...[get-define CC_FOR_BUILD]" # On Darwin, we prefer to use -g0 to avoid creating .dSYM directories # but some compilers may not support it, so test here. switch -glob -- [get-define host] { *-*-darwin* { if {[cctest -cflags {-g0}]} { define cc-default-debug -g0 } } } if {![cc-check-includes stdlib.h]} { user-error "Compiler does not work. See config.log" }