Richard Thomson
Senior Software Engineer, NVIDIA
@LegalizeAdulthd
http://LegalizeAdulthood.wordpress.com
legalize@xmission.com
Outline
 Feature concerns
 Platform concerns
 Testing for C++ standard support
 Testing for C++ language features
 Testing for compiler option support
 Testing for specific headers
 Testing arbitrary code can compile
 Testing for specific functions
 Testing arbitrary code can run
 Using the results of the tests
Feature Concerns
 Does the compiler support the minimum
standard my code assumes?
 Does the compiler support the minimum
language features my code assumes?
Platform Concerns
 Does the compiler have headers my code
assumes to exist?
 Does the standard library have functions my
code assumes to exist?
 Is the compiler generating 32-bit or 64-bit
code?
 Does the compiler provide specific extensions?
C++ Standard Support
 If you need it, you need it...
 Usually you won't conditionally compile your
code for different versions of the standard
 Rely on older releases for older standards
 May want to explicitly state the assumed
standard to avoid future incompatibilities
 e.g. compile for C++11 with C++14/17/20/...
features disabled
Specifying C++ Standard for
Targets
 Target properties:
 <LANG>_STANDARD:STRING
 The requested standard version
 C: 90, 99 or 11
 CXX: 98, 11, 14, 17 (>= 3.8), or 20 (>= 3.12)
 CUDA: 98 or 11
 <LANG>_STANDARD_REQUIRED:BOOL
 Hard error if the standard is not supported
 <LANG>_EXTENSIONS:BOOL
 Enable compiler extensions
 Always set along with <LANG>_STANDARD
Specifying C++ Standard Globally
 Target properties initialized from global
properties:
 CMAKE_<LANG>_STANDARD
 CMAKE_<LANG>_STANDARD_REQUIRED
 CMAKE_<LANG>_EXTENSIONS
 Easiest way to ensure all targets are
compiled consistently
 Can override with target properties if a mix
is needed
C++ Language Features for
Targets
target_compile_features( <target>
<PRIVATE|PUBLIC|INTERFACE> feature1 [feature2...]
[<PRIVATE|PUBLIC|INTERFACE> feature3 [feature4....]]
... )
 Sets target properties COMPILE_FEATURES,
INTERFACE_COMPILE_FEATURES
 CMAKE_<LANG>_KNOWN_FEATURES contains all known
features for the compiler, e.g. cxx_alignas. Consult
documentation for supported list of language features.
 If you only use a few "modern" features, may be useful to require
specific features instead of full standard support
 The strongest requirement between standard versions and
compiler features wins, e.g. transitive compiler dependencies
Detecting and Using Optional
Features
 Use generator expressions
target_compile_features( foo PUBLIC $<$<COMPILE_FEATURES:cxx_override>:cxx_override> )
target_compile_definitions( foo PUBLIC
$<$<COMPILE_FEATURES:cxx_override>:-Dfoo_OVERRIDE=override>
$<NOT $<COMPILE_FEATURES:cxx_override>>:-Dfoo_OVERRIDE=>
)
 Use WriteCompilerDetectionHeader module:
include(WriteCompilerDetectionHeader)
write_compiler_detection_header(
FILE foo_compiler_detection.h
PREFIX foo
COMPILERS GNU Clang MSVC Intel
FEATURES cxx_override )
 Allows you to use CMAKE_CXX_STANDARD with
CMAKE_CXX_STANDARD_REQUIRED set to OFF
Check Modules
 CMake ships with a variety of check
modules
 Generally implemented by compiling a
piece of boiler plate code
 May also link and run the boiler plate
 Results are stored in cache variables
 Checks are only executed once during
initial configure
 Variables control the compilation and link
Useful Check Modules
 CheckCXXCompilerFlag
 CheckCXXSymbolExists
 CheckIncludeFileCXX
 CheckLibraryExists
 CheckPrototypeDefinition
 CheckStructHasMember
 CheckTypeSize
 CheckCXXSourceCompiles
 CheckCXXSourceRuns
Variables Affecting Check Modules
 Variables affecting the boiler plate:
 CMAKE_REQUIRED_FLAGS
 CMAKE_REQUIRED_DEFINITIONS
 CMAKE_REQUIRED_INCLUDES
 CMAKE_REQUIRED_LIBRARIES
 CMAKE_REQUIRED_QUIET
 Set the variables before calling the specific
check command
 CMake does not use these variables for
regular compilation
Testing for C++ Compiler Flags
 Maybe you want an Address Sanitizer
(ASan) build if the compiler supports it
 Support varies based on compiler version
 Testing compiler and version combinations
is tedious and fragile
 CheckCXXCompilerFlag module:
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag(-fsanitize=address
ASanSupported)
if(ASanSupported)
# ...
endif()
Testing for Specific Functions
 CheckCXXSymbolExists can find a symbol
that identifies a macro, variable or C style
function using the C++ compiler.
 CheckPrototypeDefinition can check that a
declared C function matches the expected
prototype
check_prototype_definition(getpwent_r
"struct passwd *getpwent_r(struct passwd *src, char *buf, int buflen)"
"NULL"
"unistd.h;pwd.h"
SOLARIS_GETPWENT_R)
 Use CheckCXXSourceCompiles for other
cases
Testing for Specific Headers
 CheckIncludeFileCXX validates that a
header can be included in C++
include(CheckIncludeFileCXX)
check_include_file_cxx("sstream" haveSStream)
 CheckIncludeFile, CheckIncludeFiles
provides similar functionality for C headers
Testing for Specific Libraries
 CheckLibraryExists can find a function in a
library
 CheckCXXSourceCompiles can be used to
attempt to call a function in a library and link
against it
Testing Code Compiles
 CheckCSourceCompiles for C
 CheckCXXSourceCompiles for C++
include(CheckCXXSourceCompiles)
check_cxx_source_compiles("
int main(int argc, char *argv[])
{
int myVar;
return 0;
}" noWarnUnused FAIL_REGEX "[Ww]arn")
if(noWarnUnused)
message("Unused variables don't warn")
endif()
Testing Code Runs
 CheckCSourceRuns for C
 CheckCXXSourceRuns for C++
include(CheckCXXSourceRuns)
check_cxx_source_runs("
int main()
{
return 1; // still runs OK
}" runsOK)
if(runsOK)
message("Code ran OK")
endif()
What To Do With The Results?
 Checks result in variables being set
 Use the variables in generator expressions
 Use the variables in target properties
 Use the variables to configure header files
Generator Expressions
 Used during build system generation to
produce information specific to a build
configuration
 Use for information that varies between debug
or release, for instance
target_compile_definitions(
foo PUBLIC <$<CONFIG:Debug>:DEBUG>)
 Be careful about sense of true/false
 CMake considers 1, ON, TRUE, YES to be truthy
 Generator expressions only consider 1 to be truthy
 Rich syntax, see documentation for full details
Common Generator
Expressions
 $<BOOL:...> Evaluate CMake truthy
 $<AND:?[,?]...> 1 if all ?s are 1
 $<OR:?[,?]...> 1 if any ?s are 1
 $<NOT:?> 1 if ? is 0, 0 if ? is 1
 $<TARGET_EXISTS:tgt> 1 if target tgt
exists
 $<CONFIG:cfg> 1 if cfg is config
 $<PLATFORM_ID:id> 1 if id is platform
 $<COMPILER_ID:id> 1 if id is compiler
Informational Generator
Expressions
 Return some kind of information instead of
0,1
 $<CONFIG> Configuration name
 $<PLATFORM_ID> Platform name
 $<COMPILER_ID> Compiler name
 $<TARGET_FILE:tgt> Full path to target
 $<TARGET_PROPERTY:tgt,prop> Value of
prop
Output Generator Expressions
 Combine inputs to produce an output
 $<0:?> Empty string (ignores ?)
 $<1:?> Content of ?
 $<JOIN:list,glue> Join items in list with
glue
 $<LOWER_CASE:?> ? converted to
lower case
 $<UPPER_CASE:?> ? converted to
upper case
 $<MAKE_C_IDENTIFIER:?> ? as C
identifier
Recommended Overall
Strategy
 Test for things you use
 Don't test for OS/version combinations
 Don't test for compiler/version combinations
 For things you need, fail if not found
 For optional things, write a configuration
header
Configuring Files
cmake_configure_file( <input> <output>
[COPYONLY] [ESCAPE_QUOTES] [@ONLY]
[NEWLINE_STYLE
[UNIX|DOS|WIN32|LF|CRLF] ]
)
 Writes an output file from an input file
template, substituting variable references in
the template.
 Template in the source tree, e.g. foo.h.in
 Output in the binary tree, e.g. foo.h
Common configure_file Constructs
 #cmakedefine VAR ...
Depending on truthiness of VAR, expands to
○ #define VAR ...
○ /* #undef VAR */
 #cmakedefine01 VAR
Depending on truthiness of VAR, expands to
○ #define VAR 1
○ #define VAR 0
 @VAR@ expands to value of VAR, without
@s
configure_file Example
Template
// foo_features.h
#pragma once
#cmakedefine01 HAVE_STD_MEMCMP
#cmakedefine01 HAVE_STD_STRCMP
#cmakedefine01 HAVE_C_MEMCMP
#cmakedefine01 HAVE_C_STRCMP
#cmakedefine01 HAVE_C_BCMP
configure_file Set Variables
include(CheckSymbolExists)
include(CheckCXXSourceCompiles)
check_cxx_source_compiles([=[
#include <cstring>
int main() { return std::memcmp("", "", 0); }
]=] HAVE_STD_MEMCMP)
check_cxx_source_compiles([=[
#include <cstring>
int main() { return std::strcmp("", ""); }
]=] HAVE_STD_STRCMP)
check_symbol_exists("memcmp" "string.h" HAVE_C_MEMCMP)
check_symbol_exists("strcmp" "string.h" HAVE_C_STRCMP)
check_symbol_exists("bcmp" "strings.h" HAVE_C_BCMP)
configure_file Example Usage
configure_file(foo_features.h.in foo_features.h)
add_executable(foo foo.cpp
"${CMAKE_CURRENT_BINARY_DIR}/foo_features.h")
target_include_directories(foo
PRIVATE "${CMAKE_CURRENT_BINARY_DIR}")
configure_file CMake Output
-- Performing Test HAVE_STD_MEMCMP
-- Performing Test HAVE_STD_MEMCMP - Success
-- Performing Test HAVE_STD_STRCMP
-- Performing Test HAVE_STD_STRCMP - Success
-- Looking for memcmp
-- Looking for memcmp - found
-- Looking for strcmp
-- Looking for strcmp - found
-- Looking for bcmp
-- Looking for bcmp - not found
-- Configuring done
-- Generating done
configure_file Resulting
Header
// foo_features.h
#pragma once
#define HAVE_STD_MEMCMP 1
#define HAVE_STD_STRCMP 1
#define HAVE_C_MEMCMP 1
#define HAVE_C_STRCMP 1
#define HAVE_C_BCMP 0
Using Configured Header
#include <foo_features.h>
#include <iostream>
#if HAVE_STD_MEMCMP || HAVE_STD_STRCMP
#include <cstring>
#endif
#if HAVE_C_MEMCMP || HAVE_C_STRCMP
#include <string.h>
#endif
#if HAVE_C_BCMP
#include <strings.h>
#endif
int main()
{
#if HAVE_STD_MEMCMP
std::cout << "Have std::memcmpn";
#endif
#if HAVE_STD_STRCMP
std::cout << "Have std::strcmpn";
#endif
#if HAVE_C_MEMCMP
std::cout << "Have C memcmpn";
#endif
#if HAVE_C_STRCMP
std::cout << "Have C strcmpn";
#endif
#if HAVE_C_BCMP
std::cout << "Have C bcmpn";
#endif
return 0;
}
Program Output
Have std::memcmp
Have std::strcmp
Have C memcmp
Have C strcmp

Feature and platform testing with CMake

  • 1.
    Richard Thomson Senior SoftwareEngineer, NVIDIA @LegalizeAdulthd http://LegalizeAdulthood.wordpress.com legalize@xmission.com
  • 2.
    Outline  Feature concerns Platform concerns  Testing for C++ standard support  Testing for C++ language features  Testing for compiler option support  Testing for specific headers  Testing arbitrary code can compile  Testing for specific functions  Testing arbitrary code can run  Using the results of the tests
  • 3.
    Feature Concerns  Doesthe compiler support the minimum standard my code assumes?  Does the compiler support the minimum language features my code assumes?
  • 4.
    Platform Concerns  Doesthe compiler have headers my code assumes to exist?  Does the standard library have functions my code assumes to exist?  Is the compiler generating 32-bit or 64-bit code?  Does the compiler provide specific extensions?
  • 5.
    C++ Standard Support If you need it, you need it...  Usually you won't conditionally compile your code for different versions of the standard  Rely on older releases for older standards  May want to explicitly state the assumed standard to avoid future incompatibilities  e.g. compile for C++11 with C++14/17/20/... features disabled
  • 6.
    Specifying C++ Standardfor Targets  Target properties:  <LANG>_STANDARD:STRING  The requested standard version  C: 90, 99 or 11  CXX: 98, 11, 14, 17 (>= 3.8), or 20 (>= 3.12)  CUDA: 98 or 11  <LANG>_STANDARD_REQUIRED:BOOL  Hard error if the standard is not supported  <LANG>_EXTENSIONS:BOOL  Enable compiler extensions  Always set along with <LANG>_STANDARD
  • 7.
    Specifying C++ StandardGlobally  Target properties initialized from global properties:  CMAKE_<LANG>_STANDARD  CMAKE_<LANG>_STANDARD_REQUIRED  CMAKE_<LANG>_EXTENSIONS  Easiest way to ensure all targets are compiled consistently  Can override with target properties if a mix is needed
  • 8.
    C++ Language Featuresfor Targets target_compile_features( <target> <PRIVATE|PUBLIC|INTERFACE> feature1 [feature2...] [<PRIVATE|PUBLIC|INTERFACE> feature3 [feature4....]] ... )  Sets target properties COMPILE_FEATURES, INTERFACE_COMPILE_FEATURES  CMAKE_<LANG>_KNOWN_FEATURES contains all known features for the compiler, e.g. cxx_alignas. Consult documentation for supported list of language features.  If you only use a few "modern" features, may be useful to require specific features instead of full standard support  The strongest requirement between standard versions and compiler features wins, e.g. transitive compiler dependencies
  • 9.
    Detecting and UsingOptional Features  Use generator expressions target_compile_features( foo PUBLIC $<$<COMPILE_FEATURES:cxx_override>:cxx_override> ) target_compile_definitions( foo PUBLIC $<$<COMPILE_FEATURES:cxx_override>:-Dfoo_OVERRIDE=override> $<NOT $<COMPILE_FEATURES:cxx_override>>:-Dfoo_OVERRIDE=> )  Use WriteCompilerDetectionHeader module: include(WriteCompilerDetectionHeader) write_compiler_detection_header( FILE foo_compiler_detection.h PREFIX foo COMPILERS GNU Clang MSVC Intel FEATURES cxx_override )  Allows you to use CMAKE_CXX_STANDARD with CMAKE_CXX_STANDARD_REQUIRED set to OFF
  • 10.
    Check Modules  CMakeships with a variety of check modules  Generally implemented by compiling a piece of boiler plate code  May also link and run the boiler plate  Results are stored in cache variables  Checks are only executed once during initial configure  Variables control the compilation and link
  • 11.
    Useful Check Modules CheckCXXCompilerFlag  CheckCXXSymbolExists  CheckIncludeFileCXX  CheckLibraryExists  CheckPrototypeDefinition  CheckStructHasMember  CheckTypeSize  CheckCXXSourceCompiles  CheckCXXSourceRuns
  • 12.
    Variables Affecting CheckModules  Variables affecting the boiler plate:  CMAKE_REQUIRED_FLAGS  CMAKE_REQUIRED_DEFINITIONS  CMAKE_REQUIRED_INCLUDES  CMAKE_REQUIRED_LIBRARIES  CMAKE_REQUIRED_QUIET  Set the variables before calling the specific check command  CMake does not use these variables for regular compilation
  • 13.
    Testing for C++Compiler Flags  Maybe you want an Address Sanitizer (ASan) build if the compiler supports it  Support varies based on compiler version  Testing compiler and version combinations is tedious and fragile  CheckCXXCompilerFlag module: include(CheckCXXCompilerFlag) check_cxx_compiler_flag(-fsanitize=address ASanSupported) if(ASanSupported) # ... endif()
  • 14.
    Testing for SpecificFunctions  CheckCXXSymbolExists can find a symbol that identifies a macro, variable or C style function using the C++ compiler.  CheckPrototypeDefinition can check that a declared C function matches the expected prototype check_prototype_definition(getpwent_r "struct passwd *getpwent_r(struct passwd *src, char *buf, int buflen)" "NULL" "unistd.h;pwd.h" SOLARIS_GETPWENT_R)  Use CheckCXXSourceCompiles for other cases
  • 15.
    Testing for SpecificHeaders  CheckIncludeFileCXX validates that a header can be included in C++ include(CheckIncludeFileCXX) check_include_file_cxx("sstream" haveSStream)  CheckIncludeFile, CheckIncludeFiles provides similar functionality for C headers
  • 16.
    Testing for SpecificLibraries  CheckLibraryExists can find a function in a library  CheckCXXSourceCompiles can be used to attempt to call a function in a library and link against it
  • 17.
    Testing Code Compiles CheckCSourceCompiles for C  CheckCXXSourceCompiles for C++ include(CheckCXXSourceCompiles) check_cxx_source_compiles(" int main(int argc, char *argv[]) { int myVar; return 0; }" noWarnUnused FAIL_REGEX "[Ww]arn") if(noWarnUnused) message("Unused variables don't warn") endif()
  • 18.
    Testing Code Runs CheckCSourceRuns for C  CheckCXXSourceRuns for C++ include(CheckCXXSourceRuns) check_cxx_source_runs(" int main() { return 1; // still runs OK }" runsOK) if(runsOK) message("Code ran OK") endif()
  • 19.
    What To DoWith The Results?  Checks result in variables being set  Use the variables in generator expressions  Use the variables in target properties  Use the variables to configure header files
  • 20.
    Generator Expressions  Usedduring build system generation to produce information specific to a build configuration  Use for information that varies between debug or release, for instance target_compile_definitions( foo PUBLIC <$<CONFIG:Debug>:DEBUG>)  Be careful about sense of true/false  CMake considers 1, ON, TRUE, YES to be truthy  Generator expressions only consider 1 to be truthy  Rich syntax, see documentation for full details
  • 21.
    Common Generator Expressions  $<BOOL:...>Evaluate CMake truthy  $<AND:?[,?]...> 1 if all ?s are 1  $<OR:?[,?]...> 1 if any ?s are 1  $<NOT:?> 1 if ? is 0, 0 if ? is 1  $<TARGET_EXISTS:tgt> 1 if target tgt exists  $<CONFIG:cfg> 1 if cfg is config  $<PLATFORM_ID:id> 1 if id is platform  $<COMPILER_ID:id> 1 if id is compiler
  • 22.
    Informational Generator Expressions  Returnsome kind of information instead of 0,1  $<CONFIG> Configuration name  $<PLATFORM_ID> Platform name  $<COMPILER_ID> Compiler name  $<TARGET_FILE:tgt> Full path to target  $<TARGET_PROPERTY:tgt,prop> Value of prop
  • 23.
    Output Generator Expressions Combine inputs to produce an output  $<0:?> Empty string (ignores ?)  $<1:?> Content of ?  $<JOIN:list,glue> Join items in list with glue  $<LOWER_CASE:?> ? converted to lower case  $<UPPER_CASE:?> ? converted to upper case  $<MAKE_C_IDENTIFIER:?> ? as C identifier
  • 24.
    Recommended Overall Strategy  Testfor things you use  Don't test for OS/version combinations  Don't test for compiler/version combinations  For things you need, fail if not found  For optional things, write a configuration header
  • 25.
    Configuring Files cmake_configure_file( <input><output> [COPYONLY] [ESCAPE_QUOTES] [@ONLY] [NEWLINE_STYLE [UNIX|DOS|WIN32|LF|CRLF] ] )  Writes an output file from an input file template, substituting variable references in the template.  Template in the source tree, e.g. foo.h.in  Output in the binary tree, e.g. foo.h
  • 26.
    Common configure_file Constructs #cmakedefine VAR ... Depending on truthiness of VAR, expands to ○ #define VAR ... ○ /* #undef VAR */  #cmakedefine01 VAR Depending on truthiness of VAR, expands to ○ #define VAR 1 ○ #define VAR 0  @VAR@ expands to value of VAR, without @s
  • 27.
    configure_file Example Template // foo_features.h #pragmaonce #cmakedefine01 HAVE_STD_MEMCMP #cmakedefine01 HAVE_STD_STRCMP #cmakedefine01 HAVE_C_MEMCMP #cmakedefine01 HAVE_C_STRCMP #cmakedefine01 HAVE_C_BCMP
  • 28.
    configure_file Set Variables include(CheckSymbolExists) include(CheckCXXSourceCompiles) check_cxx_source_compiles([=[ #include<cstring> int main() { return std::memcmp("", "", 0); } ]=] HAVE_STD_MEMCMP) check_cxx_source_compiles([=[ #include <cstring> int main() { return std::strcmp("", ""); } ]=] HAVE_STD_STRCMP) check_symbol_exists("memcmp" "string.h" HAVE_C_MEMCMP) check_symbol_exists("strcmp" "string.h" HAVE_C_STRCMP) check_symbol_exists("bcmp" "strings.h" HAVE_C_BCMP)
  • 29.
    configure_file Example Usage configure_file(foo_features.h.infoo_features.h) add_executable(foo foo.cpp "${CMAKE_CURRENT_BINARY_DIR}/foo_features.h") target_include_directories(foo PRIVATE "${CMAKE_CURRENT_BINARY_DIR}")
  • 30.
    configure_file CMake Output --Performing Test HAVE_STD_MEMCMP -- Performing Test HAVE_STD_MEMCMP - Success -- Performing Test HAVE_STD_STRCMP -- Performing Test HAVE_STD_STRCMP - Success -- Looking for memcmp -- Looking for memcmp - found -- Looking for strcmp -- Looking for strcmp - found -- Looking for bcmp -- Looking for bcmp - not found -- Configuring done -- Generating done
  • 31.
    configure_file Resulting Header // foo_features.h #pragmaonce #define HAVE_STD_MEMCMP 1 #define HAVE_STD_STRCMP 1 #define HAVE_C_MEMCMP 1 #define HAVE_C_STRCMP 1 #define HAVE_C_BCMP 0
  • 32.
    Using Configured Header #include<foo_features.h> #include <iostream> #if HAVE_STD_MEMCMP || HAVE_STD_STRCMP #include <cstring> #endif #if HAVE_C_MEMCMP || HAVE_C_STRCMP #include <string.h> #endif #if HAVE_C_BCMP #include <strings.h> #endif int main() { #if HAVE_STD_MEMCMP std::cout << "Have std::memcmpn"; #endif #if HAVE_STD_STRCMP std::cout << "Have std::strcmpn"; #endif #if HAVE_C_MEMCMP std::cout << "Have C memcmpn"; #endif #if HAVE_C_STRCMP std::cout << "Have C strcmpn"; #endif #if HAVE_C_BCMP std::cout << "Have C bcmpn"; #endif return 0; }
  • 33.
    Program Output Have std::memcmp Havestd::strcmp Have C memcmp Have C strcmp