Oct 11, 2016

[cmake] best practice



FetchContent

Reference:
https://cmake.org/cmake/help/latest/guide/using-dependencies/index.html
The major usage for downloading 3rd party library from source control.



Antipattern:

  • set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -Wextra")
  • Debug build (Adding -g manually)
    • Pass -DCMAKE_BUILD_TYPE=debug to CMake
    • CMAKE_BUILD_TYPE / CMAKE_CONFIGURATION_TYPES
      • This variable is only meaningful to single-configuration generators (such as Makefile Generators and Ninja)
      • There are many per-config properties and variables (usually following clean SOME_VAR_<CONFIG> order conventions), such as CMAKE_C_FLAGS_<CONFIG>, specified as uppercase: CMAKE_C_FLAGS_[DEBUG|RELEASE|RELWITHDEBINFO|MINSIZEREL].
        • DEBUG
        • RELEASE
        • RELWITHDEBINFO
        • MINSIZEREL
  • use find_library instead of writing:
  • Do not do:
    • add_executable(myexe myexe.c myexe.h) // gcc/clang will find xxx.c 's xxx.h/hpp automatically.
    • The only exception to this rule is when you generate header files as part of your build.
  • Do not repeat this:
  • Do not 'Invoking make targets' intently. 
    • Invoke targets directly in CMake.
      Most custom commands have a DEPENDS option that can be used to invoke other targets.
  • Do Out-of-source build.
  • Do not call out sed or shell pipelines in Cmakelists.txt. Call cmake in shell script.
  • Do not invoke cmake multiple times. It's just _not_ necessary.
    Ninja or make will detect src code changes.

Compiler and platform test results are cached in CMakeCache.txt
You can modify this file and re-run CMake.
CMake also provides graphical cache editors.


cmake[<options>]<path> 
  • If the specified path contains a CMakeCache.txt, it is treated as a build directory where the build system is reconfigured and regenerated. 
  • Otherwise the specified path is treated as the source directory and the current working directory is treated as build directory.
cmake-E<command>[<options>...] // cmake command mode
  • Run cmake -E help for a summary of commands.
  • $ cmake -E make_directorybuild
    $ cmake -E chdir cmake -E time cmake
    $ cmake -E time cmake build
use cmake to run the build system
  • $ cmake --build build \ --target mylibrary \ --config Release \ --clean-first
cmake as a scripting language
  • Resist the bash/perl/python temptation!



Parameters needed:

  • cmake_minimum_required(VERSION 3.2 FATAL_ERROR)
    • Prefer the latest version of CMake.
  • project(<name> VERSION <version> LANGUAGES CXX)
    • CMake sets several variables based on project()
    • Call to project() must be direct, not through a function/macro/include
    • CMake will add a call to project() if not found on the top level
  • add_subdirectory(<sourcedir> [<binarydir>])
    • Embed projects as sub-projects.
    • CMake creates a Makefile/Solution for each sub-project.
    • The sub-project does not have to reside in a sub-folder.
  • Make sure that all your projects can be built both standalone and as a sub-project of another project
    • Don’t assume that your project root is the build root.
    • Don’t modify global compile/link flags. 
    • Don’t make any global changes!
  • find_package(Qt5 REQUIRED COMPONENTS Widgets)
    • Finds preinstalled dependencies
    • Can set some variables and define imported targets. 
    • Supports components
  • Add an executable target
  • Add a library target
  • Always add namespace aliases for libraries.
    https://stackoverflow.com/questions/46567646/cmake-usefulness-of-aliases
  • Don’t make libraries STATIC/SHARED unless they cannot be built otherwise.
  • Leave the control of BUILD_SHARED_LIBS to your clients!
  • PRIVATE Only used for this target
    PUBLIC Used for this target and all targets that link against it
    INTERFACE Only used for targets that link against this library

  • target_link_libraries()
    • Set libraries as dependency.

  • Things to follow
  • target_include_directories()
  • Things to follow
  • target_compile_definitions()
  • Things to follow
    • Avoid the add_definitions() command.
    • Avoid adding definitions to CMAKE_<LANG>_FLAGS

  • target_compile_options()
    • Set compile options/flags.
    • if (CMAKE_COMPILER_IS_GNUCXX) target_compile_options(foo PUBLIC -fno-elide-constructors)
      endif()
  • Things to follow
    • Wrap compiler specific options in an appropriate condition.
    • Avoid the add_compile_options() command. 
    • Avoid adding options to CMAKE_<LANG>_FLAGS.

  • target_compile_features()
    • Set required compiler features.
    • target_compile_features(
      foo PUBLIC
      cxx_auto_type
      cxx_range_for
      PRIVATE
      cxx_variadic_templates)
    • CMake will add required compile flags.
    • Errors if the compiler is not supported.
  • Things to follow
    • Don’t add -std=c++11 to CMAKE_<LANG>_FLAGS.
    • Don’t pass -std=c++11 to target_compile_options().

best practices:

  • goal: no custom variables: 
    • # BAD
      set(PROJECT foobar)
      set(LIBRARIES foo)
      if(WIN32)
      list(APPEND LIBRARIES bar)
      endif()
      target_link_libraries(${Project} ${LIBRARIES} )
    • # GOOD
      target_link_libraries(foobar PRIVATE my::foo)

      if(WIN32)
      target_link_libraries(foobar PRIVATE my::bar)
      endif()
  • don't use file(glob)!
    • # BAD
      file(GLOB sources ”*.cpp”)
      add_library(mylib ${sources})
    • file(GLOB) is useful in scripting mode. 
    • Don’t use it in configure mode
  • list all sources explicitly (Explicit is better than implicit)
    • add_library(
      mylib
      main.cpp
      file1.cpp
      file2.cpp)
  • goal: no custom functions
    • Experience taught me: Most of the time it is a bad idea.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.