Showing posts with label cmake. Show all posts
Showing posts with label cmake. Show all posts

Jun 19, 2022

[CMake] Modern CMake in style

Reference:
Modern CMake Modules - Bret Brown - CppCon 2021
CppCon 2017: Mathieu Ropert “Using Modern CMake Patterns to Enforce a Good Modular Design”


$ clang++ main.cpp -o vsdmars -Wreturn-type
is same as:
target_compile_options(
    vsdmars PRIVATE
    $<$<COMPILE_LANG_AND_ID:CXX,Clang,GNU>:-Werror=return-type>
)

Find Modules

Loaded by the find_package()
Find<PackageName>.cmake // can have find_library(...) command


Use Modern CMake

  1. declare module with ADD_LIBRARY or ADD_EXECUTABLE
  2. declare build flags with TARGET_xxx()
  3. declare dependencies with TARGET_LINK_LIBRARIES
  4. Specify what is PUBLIC and what is PRIVATE
  5. Consider people are using your library through CMake


Don't

  1. breaking cmake target model
  2. breaking cmake features
    dev workflows
    functions
    standard modules
  3. making changes in source directories
  4. avoid version control operations
  5. toolchain details
    linkers
    compiler
  6. build requires magic flags like -DMAGIC_VAR
  7. Hijacking CMake variables
  8. Requiring special build targets
  9. Expecting certain environments
$ cmake && cmake --build && ctest && cmake --install


Write a module

name the file

vsdmars.cmake // include(vsdmars)
FindVsdmars.cmake // find_package(vsdmars REQUIRED)
vsdmarsConfig.cmake // find_package(vsdmars REQUIRED) <--- Most powerful

e.g.
vsdmarsConfig.cmake
function(target_needs_news)
    cmake_parse_arguments(parsed
        ""             #   options
        "TARGET"       # one-value keywords
        ""             # multi-value keywords
        ${ARGN}        # strings to parse
    )
    # TODO: defensive argument parsing
    set(target ${parsed_TARGET})
    message(VERBOSE "done, ${target}")
endfunction()


Defensive argument parsing

if(parsed_UNPARSED_ARGUMENTS)
    message(FATAL_ERROR
        "BAD ARGUMENT: ${parsed_UNPARSED_ARGUMENTS}"
    )
endif()

if(parsed_KEYWORDS_MISSING_VALUES)
    message(...)
endif()


Essential for CMake modules

  1. API documentation
    README
  2. Testing the cmake module we just wrote
    https://crascit.com/2016/10/18/test-fixtures-with-cmake-ctest/
  3. message() with verbosity
$ cmake --log-level=verbose


Installing a CMake modules

  1. share/cmake/vsdmars/vsdmarsConfig.cmake
  2. ship version files as well if needed


CMakeLists.txt for CMake modules

cmake_minimum_required(VERSION 3.21)
project(vsdmars LANGUAGES NONE) # if module is lang agnostic
enable_testing() # every CMakeLists.txt should have this

install(
    FILES vsdmarsConfig.cmake
    DESTINATION share/cmake/vsdmars
    COMPONENT vsdmars
)


Development Setup

CMAKE_GENERATOR=Ninja
CMAKE_TOOLCHAIN_FILE=path/to/ToolChain.cmake
CMAKE_BUILD_TYPE=Debug
DESTDIR=your/staging/dir


Development Workflow

$ git clone ...
$ cmake -B build -S vsdmars
$ cmake --build build
$ ctest --test-dir build
$ cmake --instal build \
    --prefix /opt/...
    --component vsdmars



Integration Workflow

$ git clone ...
$ cmake -B cxx-build \
    -S cxx-project \
    -DVSDMARS_DIR=./vsdmars/
$ cmake --build cxx-build
$ ctest --ctest-dir cxx-build
$ cmake --install cxx-build \
    --prefix /opt/vsdmars \
    --component cxx-project


Build flags don't scale

  • every change in public flags has to be propagated upwards
  • Most people give up and put every include director in a common/root build file.
  • Correct way of doing so is to put all 3rd party
    my/include/namespace
    into
    ./include/my/include/namespace


Modern build systems

  • Forbid/report circular and hidden dependencies
  • Help developer reason at module level
  • Do more than build as you are told


How?

  • Define your module build flags
  • Define your module dependencies
  • Keep out of other modules internals
  • Each module has a set of private flags
  • Each module has a set of public flags(required to build against its interface)
  • Build interfaces are transitive
  • Private means .cpp includes other headers
  • Public means .h includes other headers
  • Build flags are not gone but encapsulated
  • Can still with CPPFLAGS, CXXFLAGS and LDFLAGS, and external flags are not our concern anymore
  • Declare module with ADD_LIBRARY or ADD_EXECUTABLE
  • Declare build flags with TARGET_xxx()
  • Declare dependencies with TARGET_LINK_LIBRARIES
  • Specify what is PUBLIC and PRIVATE


Global setup

cmake_minimum_required(VERSION 3)
add_compile_options(-Wall -Werror etc.) // including flags effect ABI
add_library(vsdmarsLib src/xxx.cpp)



Declare flags

target_include_directories(vsdmarsLib PUBLIC include)
target_include_directories(vsdmarsLib PRIVATE src)

if (SOME_SETTING)
    target_compile_definitions(vsdmarsLIB
        PUBLIC WITH_SOME_SETTING
endif()


Declare dependencies

target_link_libraries(vsdmarsLib PUBLIC abc)
target_link_libraries(vsdmarsLib PRIVATE xyz)


Header only libraries

# nothing to build actually, just export headers during install/being pulled by other modules
add_library(vsdmarsLibHeaderOnly INTERFACE)
target_include_directories(vsdmarsLibHeaderOnly INTERFACE include)
target_link_libraries(vsdmarsLibHeaderOnly INTERFACE Boost::Boost)



Antipatterns



3rd party

cmake_minimum_required(VERSION 3.5)
find_package(GTest)
add_executable(foo ...)
target_link_libraries(foo GTest::GTest GTest::Main)
find_library(XXX_LIB xxx HINTS ${XXX_DIR}/lib)
add_library(xxx SHARED IMPORTED LOCATION ${XXX_LIB})
target_include_directories(xxx INTERFACE ${XXX_DIR}/include)
target_link_libraries(xxx INTERFACE Boost::boost)

May 28, 2021

[cmake] commands


Reference:
https://cprieto.com/posts/2016/10/cmake-out-of-source-build.html



out-of-dir build:
$ cmake --build build_dir --clean-first


Debugging find module:
$cmake -DCMAKE_FIND_DEBUG_MODE=ON -G Ninja ../

Show compile commands:
$ cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON path/to/llvm/sources

Change compiler:
$ -DCMAKE_C_COMPILER=/path/to/clang
$ -DCMAKE_CXX_COMPILER=/path/to/clang++

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.

Jun 23, 2012

[CMake] information q&a

autoheader discussion
autoheader discussion - 2
CMake linking problem
some variable to notice:
CMAKE_EXE_LINKER_FLAGS
pkg-config usage:
g++ -Wall $(pkg-config --cflags --libs gstreamer-0.10) main.cpp -o MPEG4GStreamer

usage of FIND_PACKAGE_HANDLE_STANDARD_ARGS


Some files under /Modules to look into:
CMakePackageConfigHelpers.cmake
WriteBasicConfigVersionFile.cmake(obsolete)

some Findxxx.cmake files

Aug 28, 2011

[Cmake] CMAKE_INSTALL_PREFIX issue


I want to setup a default value for CMAKE_INSTALL_PREFIX
if the user did not indicate a value.

so I put:

IF (NOT CMAKE_INSTALL_PREFIX)
  SET(CMAKE_INSTALL_PREFIX "/opt/${PACKAGE_NAME}-${PACKAGE_VERSION}"
CACHE PATH "Install path prefix" FORCE)
ELSE (NOT CMAKE_INSTALL_PREFIX)
  MESSAGE("CMAKE_INSTALL_PREFIX already set to: ${CMAKE_INSTALL_PREFIX}")
ENDIF (NOT CMAKE_INSTALL_PREFIX)

but is seems that CMAKE_INSTALL_PREFIX is always set in cache
BEFORE I test this :((

What is the good way to change the default value of CMAKE_INSTALL_PREFIX
besides the usual:

cmake -DCMAKE_INSTALL_PREFIX=/opt/ ../mysource

I wanted the cmake run as simple as possible like

cmake /path/to/source
--------

This question has come up several times and I've been deferring a fix
until a project-specified initial cache feature is implemented.  That
feature has been delayed because it is tricky.

I've just added a fix to CVS CMake for this.  It will be in 2.4.4 (the
upcoming patch release).  Basically your code needs to know whether the
current value of CMAKE_INSTALL_PREFIX was set by the user on the command
line or initialized by CMake.  Now when CMake initializes the variable
it also sets CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT to true.  This
enables projects to change the default install prefix like this:

IF(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
  SET(CMAKE_INSTALL_PREFIX
    "/opt/foo" CACHE PATH "FOO install prefix" FORCE
    )
ENDIF(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)

----
> Does it do this for all internal cmake variables or only for CMAKE_INSTALL_PREFIX ?

Just the install prefix.  I don't want to do it for every variable.  For
that we need a PreLoad.cmake feature (which is partially implemented but
not really working) to have project-specific initial cache entries.  I
just put this one in as a hack because so many users have asked for this
specific capability and it is a simple change before the 2.4.4 release.

-Brad
-----
Try dropping the Boolean logic and FORCE.  i.e., use just

SET(CMAKE_INSTALL_PREFIX "/opt/${PACKAGE_NAME}-${PACKAGE_VERSION}"
CACHE PATH "Install path prefix")

That style works for us to provide a default option that can be changed by
the user.  I assume that style will also work to override a variable that has
already been cached, but I don't know for sure.

Alan
=======================================
CMAKE_INSTALL_PREFIX: cmake ../kdelibs -DCMAKE_INSTALL_PREFIX=/opt/kde4 is the equivalent to ./configure --prefix=/opt/kde4

[Cmake] How Find_package works

usually include directory should be like this:
include_directories(${BZIP_INCLUDE_DIRS})


libraries are like this:
set(LIBS ${LIBS} ${LibXML++_LIBRARIES})


the package is found , the variable looks like this:
BZIP2_FOUND


---
the library/executable 's link libraries:
target_link_libraries(exampleProgram ${LIBS})


our own package.cmake files:
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/")
set this before find_package, of course.
also read:
http://vsdmars.blogspot.com/2011/08/cmakekde-cmakeliststxt-for-building-kde.html


find package's components:
find_package(Qt COMPONENTS QtOpenGL QtXml REQUIRED)


we can omit the REQUIRED keyword, if the package is optional for your project. In this case, you can use the __FOUND variable (e.g. Qt_QtXml_FOUND) 


Same:

find_package(Qt COMPONENTS QtOpenGL QtXml REQUIRED)
find_package(Qt REQUIRED COMPONENTS QtOpenGL QtXml)
find_package(Qt REQUIRED QtOpenGL QtXml)




If you only require some components of a package, and want to use others only, if they are available, you can call find_package twice:
find_package(Qt COMPONENTS QtXml REQUIRED)
find_package(Qt COMPONENTS QtOpenGL)
Alternatively, you can invoke find_package once with all components, but without theREQUIRED keyword and then explicitly check the required components:
find_package(Qt COMPONENTS QtOpenGL QtXml)
if ( NOT Qt_FOUND OR NOT QtXml_FOUND )
  message(FATAL_ERROR "Package Qt and component QtXml required, but not found!")
endif( NOT Qt_FOUND OR NOT QtXml_FOUND )
---
Find_Package steps:
First CMake checks all directories in ${CMAKE_MODULE_PATH}, then it looks in its own module directory /share/cmake-x.y/Modules/.
If no such file is found, it looks for Config.cmake or -config.cmake, which are supposed to be installed by libraries (but there are currently not yet many libraries which install them) and that don't do detection, but rather just contain hardcoded values for the installed library.


No matter which mode is used, if the package has been found, a set of variables will be defined:
  • _FOUND
  • _INCLUDE_DIRS or _INCLUDES
  • _LIBRARIES or _LIBRARIES or _LIBS
  • _DEFINITIONS
All this takes place in the Find.cmake file.

Now, in the CMakeLists.txt file in the top level directory of your code (the client code that is actually going to make use of the library , we check for the variable _FOUND to see whether the package has been found or not. For most packages the resulting variables use the name of the package all uppercased, e.g. LIBFOO_FOUND, for some packages the exact case of the package is used, e.g. LibFoo_FOUND. If this variable is found, then, we pass the_INCLUDE_DIRS to the include_directories() command and _LIBRARIES to the target_link_libraries() command of CMake.

pkg-config is used.







Aug 26, 2011

[CMake] command line

cmake --help-module-list


cmake --help-module FindKDE4
---
module path : /usr/share/cmake/Modules/
---
use cmake and make VERBOSE=1 to verify that the correct flags are being passed to the compiler and linker.


pkg-config : used for finding library information

[CMake] CMakeLists.txt Template and explained

cmake_minimum_required (VERSION 2.8)
project (KDE Project)


# The version number.
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 0)

# configure a header file to pass some of the CMake settings
# to the source code
configure_file (
  "${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
  "${PROJECT_BINARY_DIR}/TutorialConfig.h"
)

#In TutorialConfig.h.in : this is used for mapping previous {
# set (Tutorial_VERSION_MAJOR 1)
# set (Tutorial_VERSION_MINOR 0)
# }
# Cmake will generate a TutorialConfig.h under PROJECT_BINARY_DIR
# So we need to use include_directories to include this path for g++ to use as a include search path.
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
#

#
# Include path that g++ will include it as the .h search path
include_directories("${PROJECT_BINARY_DIR}")

# In MathFunctions directory, we need another CmakeLists file contain
# add_library(MathFunctions mysqrt.cxx)
# MathFunctions is a directory, mysqrt.cxx is the .cxx file in that directory ready for compile.


# ask g++ to include  MathFunctions directory for header search.
include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")

# ask g++ to build this directory
#The CMakeLists.txt file in the specified source directory will be processed immediately by CMake before processing in the current input file continues beyond this command.
add_subdirectory (MathFunctions)


#-------------------
# should we use our own math functions?
option (USE_MYMATH
        "Use tutorial provided math implementation" ON)
# in TutorialConfig.h.in we write:
# #cmakedefine USE_MYMATH
# to make our source file use this USE_MYMATH define also! (defined in TutorialConfig.h, so our source code should include this file)



# add the MathFunctions library?
#
if (USE_MYMATH)
  include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")
  add_subdirectory (MathFunctions)
  set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
endif (USE_MYMATH)

add_executable(RunMe main.cpp)
# ask g++ to use -lMathFunction , that is, to use this library to build RunMe application.
target_link_libraries (RunMe ${EXTRA_LIBS})



#---INSTALL----  make install will do the following:
# In MathFunctions directory's CMakeLists file we add:
# install (TARGETS MathFunctions DESTINATION bin)
# install (FILES MathFunctions.h DESTINATION include)

#for RunMe application:
install (TARGETS RunMe DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"      
         DESTINATION include)

# The CMake variable CMAKE_INSTALL_PREFIX is used to determine the root of where the files will be installed.



#---TEST----
# TutorialRuns : Variable
# RunMe : Application
# 25 : arguments
add_test (TutorialRuns RunMe 25)


set_tests_properties (TutorialRuns
  PROPERTIES PASS_REGULAR_EXPRESSION "25 is 5")

#---TEST Macro ---
#define a macro to simplify adding tests, then use it
macro (do_test arg result)
  add_test (TutorialComp${arg} Tutorial ${arg})
  set_tests_properties (TutorialComp${arg}
    PROPERTIES PASS_REGULAR_EXPRESSION ${result})
endmacro (do_test)

# do a bunch of result based tests
do_test (25 "25 is 5")
do_test (-25 "-25 is 0")