Cmake - RicoJia/notes GitHub Wiki

========================================================================

Basics

========================================================================

  1. Motivation:

    • Common Linking Process:
      1. compile each cpp files (functions will be placeholders for linking to solve).
      2. Link them.
    • Also, Modifications made to these files = recompilation
    • Cmake maintains a tree structure compilation order, only the necessary ones will get recompiled.
      • makefile is already doing that
    • Cmake vs Makefile: C++ compilation is not the same across platforms, such as MS windows(VScode), Linux. CMake generates Make files cmake, across platforms
  2. How it works

    1. Read CMakeList.txt, and generate files in specified locations.
    2. Root_dir has /build and CMakeList.txt
      • cmake will generate files from the CMakeList Directory.
  3. General Cautions

    1. Commands, function, macro names,are case insensitive. variable names are case sensitive!!

========================================================================

Examples

========================================================================

  • Installationcmake --version
  1. Basic workflow
    cd Build
    #This will generate CMakeCache.txt(subsequent build of the proj) and Makefile(for building the project)
    #If you already have Makefile from previous builds, cmake.. can be omitted
    #So If you change one source file, you don't have to run cmake..
    Cmake ..	
    # Generate an executable in the build folder, USING GNU make. If you use another build system such as Ninja, do ninja
    make		
    
    # this will put the files to the path
    sudo make install	
  • Build in Debug and Release mode
    • If you don't have any external Libraries as dependencies
        cmake -DCMAKE_BUILD_TYPE=Debug
        cmake -DCMAKE_BUILD_TYPE=RELEASE
      • There will be two dirs: release and Debug
        • release is faster, that's because CMake will flip the compiler flags accordingly.
    • Use an external Lib in Debug, Release
        #step 1: download the lib binaries into two folders: /somepath/debug/libXX.so and /somepath/release/libXX.So
        #step 2: in you CMakeLists.txt
        target_link_libraries(my_app
          debug /somepath/debug/libXX.so
          release /somepath/release/libXX.So
        )
        #step3 : run cmake -DCMAKE_BUILD_TYPE=debug or release, then the right lib will be linked. Note that you have to supply -DCMAKE_BUILD_TYPE, otherwise there will be errors.
    • make -C ANOTHER_DIR install is to run make install in another directory

Example with included files

  1. Simplest Example
    cmake_minimum_required(VERSION 3.0.0)	#telling CMake
    project(CALCULATOR VERSION 1.0.0)
    
    add_executable(calculator 
    main.cpp
    addition_lol.cpp
    division.cpp
    print_result.cpp
    ) #sequence of the source files do not matter, so you don't have hierarchy here.
    • Adding A library locally:
       add_library(math	#assume the headers and src files are in the same folder. 
       	addition_lol.cpp
       	division.cpp)
       target_link_libraries(calculator
      math
      print_result
       )
  2. Complete Example CMake 3.12 +
  • top level cmake
    add_subdirectory(pybind11)
    add_subdirectory(webserver)
  • sub directory Cmake
    cmake_minimum_required(VERSION 3.12)
    project(myproj)
    
    find_package(Poco REQUIRED COMPONENTS Net Util)
    
    #file is for getting all source files 
    # GLOB_RECURSE is to recursively iterate thru the sub directories
    file (GLOB_RECURSE SRC_FILES src_1.cpp src_2.cpp)
    
    add_executable(MyEXE)
    
    #add source files
    target_source(MyEXE PRIVATE ${SRC_FILES})
    
    # add include directories
    # {PYBIND11_INCLUDE_DIR} is added because top level cmake has included it, also it's a convention
    # include_directories is not for a specific target! 
    target_include_directories(MyEXE Private ${PROJECT_SOURCE_DIR}/include ${PYBIND11_INCLUDE_DIR})
    target_compile_options(MyEXE PUBLIC -Werror -Wall -Wextra)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -std=c++2a -O3")    # doesn't do anything for cmake 3.1>
    set(CMAKE_CXX_STANDARD 14)        # works for 3.1!!
    add_definitions(${GCC_COMPILE_FLAGS})
    
    # we need these libraries only for the executable
    target_link_library(MyEXE PRIVATE Poco::Net Poco::Util)
  • set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14" ) has no use
  • dunder methods

Library Examples

  1. Example with an library (main.cpp does not specify the lib name yet!)
  • Directory structure
    • my_lib Directory strucuture:
        my_lib_dir/include/my_lib
        my_lib_dir/lib
    • in both lib and main.cpp, use relative path: my_lib/addition.h
    • root_dir CMakelist.txt
       cmake_minimum_required(VERSION 3.0.0)	#telling CMake
       project(CALCULATOR VERSION 1.0.0)
       add_subdirectory(my_lib)
       add_executable(calculator 
       main.cpp
       )
       target_link_libraries(calculator
       	my_lib
       )
    • in /lib
       add_library(my_lib
       	er	c/addition_lol.cpp
       	src/division.cpp
       	src/print_result.cpp
       	)
       target_include_directories(
       	my_lib PUBLIC include
       	)
  1. Example: Building a shared library
  • crow lib
     # in the lib side:
     INCLUDE_DIRECTORIES("${BOOST_DIR}/include")
     LINK_DIRECTORIES("${BOOST_DIR}/lib")	#if you have lib.a ...
     ... All the libs util depend on!
     #if you have other unbuilt lib targets, you can do add_library as well,
    
     # Storing these cache variables is a convention for parent projects to use
     set(WEBSERVER_INCLUDE_DIRS ${PYTHON_INCLUDE_DIRS} CACHE INTERNAL "")
     set(WEBSERVER_LIBRARIES ${PYTHON_LIBRARIES} CACHE INTERNAL "")
    
     # in the application side:
     INCLUDE_DIRECTORIES("${BOOST_DIR}/include")
     LINK_DIRECTORIES("${BOOST_DIR}/lib")
     add_executable(webserver_helloworld webserver_helloworld.cpp)
     add_dependencies(webserver_helloworld util)	#THIS IS GOLDEN, cmake enforces libutil.a to be built in the corresponding $CMAKE_BINARY_DIR. If you have one thread, I guess it's fine, but if you have multiple threads, this saves lives.
    
     # header only lib uses interface, which will propagate the usage requirement
     # header only lib doesn't have build_specifications cuz it doesn't need to be built
     #This just links the library, doesn't enforce the the lib to be built successfully before we link
     target_link_libraries(webserver_helloworld INTERFACE "${UTIL_LIB}" "${CMAKE_THREAD_LIBS_INIT}")	
    • Caution:
      • static libs do not carry info about its dependencies.
        • Say A links B as private, A is built as a static lib, and C links A, then C needs B added to the linker command.
        • solution: You need to do INCLUDE_DIRECTORIES, LINK_DIRECTORIES in both the lib and the target
        • Also, need to add target_link_libraries(target_name -ljpeg) FOR ALL static libs in every user of util.
          • the error message is not suggestive of where the probablem was: Experience: check the downstream applications if they have linked the libs! An error looks like:
            ../../util/libutil.a(util_decoder.cpp.o): In function `Jpeg_Compressor_t::write_JPEG_file(char const*, int)':
            /srv/ocean/util/imaging/util/util_jpeg_compressor.h:54: undefined reference to `jpeg_std_error'
            

Misc Examples

  1. Execute a command that gets run everytime (in this case proto generation)
    add_custom_command (
      OUTPUT dummy_output
      WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/some_dir
      COMMAND bash -c "python -m grpc_tools.protoc -I . --python_out=. --pyi_out=. --grpc_python_out=. YOUR_PROTO.proto"
      DEPENDS PATH_TO_YOUR_PROTO
      VERBATIM
      )
    
    add_custom_target(proto_target ALL 
                    DEPENDS image_click_proto_output)
    
    • Idea is to define a custom command in working directory, and add it to custom target so it can be run everytime.
    • Another alternative is to use execute_process. But this method does not get run if no updates are made to the CMakeLists.txt or not in a clean environment

========================================================================

Commands

========================================================================

Include Files

  1. add_subdirectory(RELATIVE_PATH)
  • build sometimes has debug and release
  1. target_include_directories(name PUBLIC include)
  • include_directories doesn't limit the scope of the include directory to the target
  • Lower level details
    1. #this automatically sets INTERFACE_INCLUDE_DIRECTORIES propertY of the lib, so in main.cpp doesn't need to worry about the path.
  1. build_specifications & usage_requirement
  • build specification is for building the library itself
    • header-only lib (interface lib) doesn't need this
  • usage_requirement requires users to turn on complier, to have libs ready.. (propagate dependencies)
  • PRIVATE, PUBLIC, INTERFACE
    • Private: when A links B as private = A uses B in its own implementation only.
    • Interface: A links B as interface = A uses B only for its public API, not for implementation (hpp counts?)
      • interface library is for header only library
      • just include crow in discovery_server build, hence no need to make it a static library - this will also solve issue
        • Also link the boost libraries that crow needs.
    • Public: combo of both.
  • Caution:
    • static libs do not carry info about its dependencies.
      • Say A links B as private, A is built as a static lib, and C links A, then C needs B added to the linker command.
      • solution: You need to do INCLUDE_DIRECTORIES, LINK_DIRECTORIES in both the lib and the target

Dependencies

  1. target_dependencies
    • set_target_properties check how to build that.
    • target_link_libraries(calculator PRIVATE math)
      • PRIVATEhere prevents libs to be accessible to other libs.
      • If A depends on B, B depends on C:
         	target_link_libraries(B PUBLIC C)
         	target_link_libraries(A PUBLIC B)
  2. add_dependencies(webserver_helloworld util)
    • THIS IS GOLDEN, cmake enforces libutil.a to be built in the corresponding $CMAKE_BINARY_DIR. If you have one thread, I guess it's fine, but if you have multiple threads, this saves lives.
    • can be used to make sure only one lib is built.

Target

  1. add_executable
  • you can have more than 1 executables
  1. Build a lib if you want to share with others
  • Make a lib
      add_library(NAME src)
      target_include_directories(name PUBLIC include)	
  • imported lib (ref)
    • Perks:
      1. the parent project just needs to find it, no need to compile it.
      2. parent project just needs to link the library, no need to include the /include -Because the INTERFACE property has indicated where the include dir is
    • Create lib that can be imported
      # Static means static library
      # IMPORTED means no dependencies are needed
      add_library(Abc STATIC IMPORTED)
    
      # a convention: use namespace
      add_library(boost::asio static IMPORTED)
    
      # we need to specify imported location, for debug and release mode
      set_target_properties(boost::asio PROPERTIES
        IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "CXX"
        IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/asio/asio.lib"
      )
    • Use imported lib
      find_package(spdlog REQUIRED)
      add_executable(MyEXE)
      target_source(MyExe "main.cpp")
      target_link_libraries(MyExe SPDLog::spdlog)
      #no need to do target_include_directories!

========================================================================

Misc

========================================================================

  1. Execute a bash Commands
    • execute_process(COMMAND "echo "holi"")
  2. make a copy with a different name:
    add_custom_command(TARGET PUBLIC example  POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/example.cpython* ${CMAKE_BINARY_DIR}/example.so)
  3. set_target_properties(new_thing PROPERTIES SUFFIX ".so.1") change suffix of the object
  4. Change compile flags:
    1. this is appending to the existing flags.
      set_source_files_properties(
        ${list_of_your_files}
        PROPERTIES
        COMPILE_FLAGS "-Wall"
      )
    2. to unset and set:
      #TODO
      UNSET(GCC_COMPILE_FLAGS) #TODO
      SET(GCC_COMPILE_FLAGS "-Wall")   #TODO
    3. target_compile_options() will change the compile options, but some flags may not be grouped into compile_options!
      • COMPILE_DEFINITIONS do not have GCC_COMPILE_FLAGS. So GCC_COMPILE_FLAGS is still in a set of flags.
    4. make verbose=1 may not show flags
    5. add_definition(GCC_COMPILE_FLAGS) actually add GCC_COMPILE_FLAGS. set(GCC_COMPILE_FLAGS) simply sets the variable, but not effective. Also, add_definition works for sub_directory as well.
      • remove definition is effective, where?

========================================================================

Cache Variables

========================================================================

  1. Basics
  • you can store some variables in CMakeCache.txt
  • Use cache variable as kind of a "default value", there are 500+ cache variables!!
    • CMake will first find the locally defined variable, then the global scoped variables.
    • cache variables are global
  1. Usage
set(A "123" CACHED STRING "This is a comment")  
message(CACHED ${A})	
message(${A})	# will return 123
set(A "000")	
message(${a})	#will return 000 as local variables will be taken first. 
  • Important usage: Set compiler flags, and pass as #define into main.
    set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -DRICO_OPTION")   #Note, CMAKE_C_FLAGS may not work. 
    add_definitions(-DRICO_OPTION"="${CMAKE_SOURCE_DIR}")   #Might work too, needs verification
    // main.cpp
    int main(){
    #ifdef RICO_OPTION  //use ```#ifndef``` if something is not defined. 
    #endif
    }
  • if you have C and C++ at the same place:
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -O3 -fPIC")
    set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -O3 -fPIC")
  1. modify a cache variable: you may use this when first installing something
set(A "123" CACHED STRING "COMMENTS")	#this will be run only during the first time cmake is run. Afterwards, the CMakeCache.txt has been created, so no set commands can change the cache variable
set(A "lol" CACHED STRING "COMMENTS")	#This is not gonna change anything. 
set(A "lol" CACHED STRING "COMMENTS" FORCE)	# This will change the cache variable (in CMakeCache), but this is not recommended,because the user may not be aware of it. when using -D
  • or use -D (the recommended way)
      cmake -Dname=hcharile ..
  1. Common cache variables
  • CMake_SOURCE_DIR vs PROJECT_SOURCE_DIR
    • CMake_SOURCE_DIR refers to the outer most CMakeList.txt for the call, PROJECT_SOURCE_DIR refers to the closes file that has project() to the current CMakeList.txt
    • Use PROJECT_SOURCE_DIR, because CMake_SOURCE_DIR depends on where you call CMakeList.txt
  • Other common variable
 CMAKE_VERSION
 CMAKE_MAJOR_VERSION	#3
 CMAKE_MINOR_VERSION	#0
 CMAKE_PATCH_VERSION #2

 CMAKE_PROJECT_NAME	#Name of the top level project 
 PROJECT_NAME	#name of the current level project (like a lib name)

 CMAKE_GENERATOR	#GNU Make, Ninja, etc. you can also use cmake -G... to do the same thing. 

 CMAKE_CURRENT_SOURCE_DIR #the directory of the CMakeList.txt 
 CMAKE_INSTALL_PREFIX	#/usr/local
  1. Compiler Flags
  • CMAKE_CXX_FLAGS is set by CMake during OS and toolchain detection. This is a cached variable
  • Common compiler flags
    • fPic: position independent code (PIC), aka position-independent executable (PIE), means generated machine code is not dependent on being located at a specific location.
      • Basics
        1. each program has its own address space. Shared Lib can use PIC, so it can execute properly without absolute address
        2. Contrary to Absolute code (must be loaded at a specific location)
        3. Uses relative addressing, whcih is slightly slower, and bigger as well
        4. In the old times, code are non-PIC. So the address space of two different shared libs might be the same.
        5. Your GCC should compile with fpic by default now
        6. some good reads
      • fPIE is for executables
        • use fPIE only if being used on executable, or a static library that will eventually linked to position-independent executable
        • use fPIC only if making a static library
  • Warnings (https://www.foonathan.net/2018/10/cmake-warnings/)
    • -Werror: treat warnings as errors
  1. Environment variable
  • also has global scopes, but not stored in cache files
  • Use
    set (ENV var value)
    $ENV{var}

========================================================================

Using a package

========================================================================

Background Knowledge: Modules

  • Basics
    • a module can be seen as a part of a CMakeList.txt, that can be reused.
    • a module has .cmake. CMakeList.txt is a "List File"
    • Use a system module: in default location /usr/local/share/..?
    include(some_module) some_module(VAR) - Make our own module - File structure: module - CMakeList.txt - my_module.Cmake - build - CMakeList.txtCmake cmake_minimum_required(3.0.2) project(my_calc) list(APPEND CMAKE_MODULE_PATH ) #MUST specify path include(my_module) - my_module.Cmake, **Does not create another function scope!** message("Hola") # this module is just another part of CMakelist.txt ```

Procedure

  1. Download, Compile, Install (install is in usr)
  2. Install is to copy items to the default location /usr/local/include/(h) and /usr/local/lib/ (src), so other ppl can find it.
    • verify by message(${CMAKE_INSTALL_PREFIX}), you will see /usr/local
    • 2 options
       #1. using the files option, to copy the my_math_addition file to the destination
       install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/my_math addition.h DESTINATION ${CMAKE_INSTALL_PREFIX}/include/my_math)```
       # Or assume you have my_math built, as a target using add_library 
       install(TARGETS my_math DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/my_math)		
- e.g, Install a package that could be included by other packages
  ```CMake
  #create my_export-config.cmake files
  install(TARGETS my_math EXPORT my_export DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/my_math)		
  # put my_export-config.cmake in the designated path. The FILE option renames my_export-config.cmake
  install(EXPORT my_export FILE my_math-config.cmake DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/my_math)	
  target_include_directories(my_math PUBLIC 
    $<INSTALL_INTERFACE:include>
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    )
  ```
  - Set Generator Expressions to change ```INTERFACE_INCLUDE_DIRECTORIES``` otherwise, when we do ```target_include_directories(my_math PUBLIC include)```, this property will be ```/home/.../ the whole path``` . So you replace it with

2. Build and install the project
- build_essentials include gcc and g++
  - ```make -j7``` means run make on 7 threads, but this might fail sometimes.
  - ```make VERBOSE=1``` will give you a full list of files included.
  - If you can't find ```.h file```, try to download -dev or -devel package
- make install for makefile in a differnt directory ```make -C DIRECTORY install```
3. Find_Package
```Cmake
find_package(ABC)	#find ABC-config.cmake in /usr/local/lib/ABC (1 of many), also, ABC_FOUND is set here!
if (ABC_FOUND)
  #do stuff
else()
  message(FATAL_ERROR "my math not found")
endif()
```
- find_package has 2 modes
  1. ```find_package(ABC CONFIG)``` is the config mode, which searches for ```ABC-config.cmake```, This is kept in ```/usr/local```
    - E.g,
    ```Cmake
    Find_Package (OpenCV REQUIRED)	#1. becareful with cases, since cmake will search for OpenCVConfig.cmake;
    # REQUIRED will make CMake throw an error if not found, else it's treated as an optional package.
    # there are no .so or .a files explicitly
    #so you need to find them using these paths by convetion:
    #libraries: XYZ_LIBRARIES / XYZ_LIBS
    #inlude Dir: XYZ_INCLUDES / XYZ_INCLUDE_DIR
    #find /usr -name *opencv*cmake
    add_executable(DisplayImage main.cpp)
    target_include_directories(DisplayImage PRIVATE ${OpenCV_INCLUDE_DIRS})	#from OpenCVConfig.cmake
    target_link_libraries(DisplayImage PRIVATE ${OpenCV_LIBS})
    ```
  2.  Find_Package also has a module mode, (we've been using the config mode so far, so module mode will try to find in **CMAKE_MODULE_DIR** for Findmy_math.cmake, a cache variable. )
  - ```find_package(ABC MODULE)``` , which searches for ```FindABC.cmake```, this is kept in your own project
  - **Saving life: If a package is not built by cmake, it still might have Find* or *config.cmake files.** 
    - or if it uses a software called ```pkg-config```, then we might be able to use the pkg. 

========================================================================

Assistive tools

========================================================================

Strings and Texts, Variables

  • Do not use the script mode (-p empty). It doesn't allow cmake commands
    message("${VAR}") does not remove ; in ${VAR}, if VAR is a list
    message(${VAR}) does remove ; and white spaces
  • A list is a ; separated string
  • Variables - all variables are of string type. You can dereference by ${VAR}
     	set (Name Bob Smith)	# This actually set Name to a list of strings: Bob; Smith.
     	set (Name Bob; Smith)	# Equivalent to the above
     	set (Name "Bob Smith")	# this gives you a single string as output
    • Dereferencing a variable
       	set(VAR OFF)
       	set(VAR2 VAR)
       	VAR2	#this prints var
       	${VAR2}	#This prints off, because ${} is Dereferencing
    • String processing: set(VAR 1 2 3 4 ...) you can use 1-N or -1...-N (right Ato left) positions for parsing the string.
      • Example
         	set(VAR a b c;d "e;f" 2.7 "Hello there")
         	list(APPEND VAR 1.6 XX)	#will add 1.6;XX to the list
         	list(REMOVE_AT VAR 2 -3)	#will remove c,
         	list(REMOVE_ITEM VAR a 2.7)	#what is REMOVE_ITEM? removes a and 2.7 from the list!
         	list(INSERT_ITEM 2 XX 2.7) #insert after position 2.
         	list (REVERSE_VAR)
         	list(REMOVE_DUPLICATES VAR)
         	list(SORT VAR)
        • every string is an item in the list, except the "Hello there" is a whole item.
        • 2.7 is a separate item.
      • Other utils
         	list(LENGTH VAR len_var)	#len_var = the number of items in the list
         	list(GET VAR 2 5 7 sublist)
         	list(SUB_LIST 2 3 sublist2)	#start at position2, length is 3.
         	list(JOIN VAR ++ str_var)	#str_var = a++b++c++d...

Conditionals

  • Basic Examples
     	if(<condition>)
     		<Commands>
     	elseif()
     	Else
     	endif()
    • keywords for TRUE: ON, YES, TRUE, Y, a non-zero number
    • keywords for FALSE: OFF, NO, FALSE, N, 0, IGNORE, NOTFOUND, "", string ending with NOT-FOUND
    • Everything else will be treated as a variable, and its value will be evalutated .
  • Tests
    • Unary tests is to see if something exists
       	if (COMMAND target_link_libraries)	#evaluates true if the given command is found
       	if (DEFINED VAR)	# evaluates true if VAR has been DEFINED
       	if (EXISTS FILE_PATH)#if file exists
    • Binary Tests
       	if (Name1 STRLESS Name2)	
       		STRGREATER
       		STREQUAL		#chars are compared one by one based on ASCII. 
    • Boolean: NOT, OR, AND

loops

  • while loop
     	while(NOT VAR STREQUAL "aaaaa")
     		set(VAR ${VAR}a)
     	endwhile()
  • For-each loop
    • Vanila
      	foreach(Name Alice Bob Chi)
      		...
      	endforeach()
    • in list
       	for_each(x IN LISTS list1 list2 list3)
       	endforeach()
    • range:
       	foreach(x RANGE 10)	#from 0-10, all inclusive 
       	foreach(x RANGE 10 20)	#from 10-20, all inclusive
       	foreach(x RANGE 10 20 3)	# step size 3. 
      • start and stop are all inclusive!!

Functions

  • Basic Use
     	function(print_detail var)
     		message("My name is ${var}")
     	endfunction()
     	print_detail(Ruotong)
  • Tricky: You might need to do ${${}} for vars
    fucntion(print_detail var)
     message(My name is ${var*})
    endfunction()
    set (name "Ruotong")
    print_detail(${name})
  • function scopes
    • In Cmake, ALL variables outside of the function will be made a local copy inside a function. Then, the function will operate on the local copy
    • Cmake functions cannot return values. work around is changing parent scope var using PARENT_SCOPE
    • E.g,
       	set (Name Charlie)
       	function (print_detail)
       		message(${Name})	#prints Charlie, because a local copy of the variable is created
       		set(Name Bob PARENT_SCOPE)	#will set parent scope
       	endfunction()
    • add_subdirectory(dir_1 dir_2) actually creates a separate function scope
       	#CMakeList_Parent.txt
       	set (Name Charlie)
       	add_subdirectory(child_dir)
       	
       	# CMakeList_child.txt
       	message(${Name})	#prints Charlie, treat this as a function scope
  • What if there are more than 1 function gets defined?
     	fuction(print_detail var) ...	#1
     	fuction(print_detail var) ...	#2
     	fuction(print_detail var) ...	#3
    
     	print_detail(name)	#3 gets called
     	_print_detail(name)	#2 gets called
     	__print_detail(name)	#Error, you can only print the last 2 definitions of a function.
  • Optional Arguments: Cmake actually has special variables for storing info about args. Also, you can supply more than args required!
     	function(print_result name)	
     		message(${ARGV0})	# print Bob
     		message(${ARGV1})	#print Alice
     		message(${ARGV2})	#prints nothing, since we only have 2 args here
    
     		message(${ARGC})	#C is for count, so 2
     		message(${ARGV})	#V is verbose, prints all args
     		message(${ARGN})	#N is optional, so Alice. 
     	endfunction()
     	print_result(Bob Alice)
  • Macros
    • Similar to functions, but 1. does string replacement 2.do not create a child scope, everything in parent scope
    macro (print_detail name) message(${name}) #Bob set name="Charlie" # Creating a new parent scope var message(${name}) #Charlie is printed, since ${name} was copied and pasted as text-replacement. Parent Scope name has been changed. endmacro() print_detail(Bob) ```

========================================================================

Misc

========================================================================

  • Commenting in Cmake

    #Comments
    #[[		
     MULTILINE COMMENTS
    #]]
    
    #uncommenting 
    ##[[
    
    ##]]
    
    # nested COMMENTS
    #[==[ This is comment line 1
     comment 1
       #[=[
       comment 2
       #]=]
    #]==]
  • run cmake in script mode, -p, in bash

    #step 1: use which Cmake to get the cmake path. say /usr/bin/cmake
    #step2: chmod +x CMakeList.txt
    #in bash file
    ./CMakeList.txt 
    #or 
    cmake -P CmakeLists.txt
  • install a python package written in C/C++ (with make install, for installing a python package) 0. structure: from xx.yy import Binary_name

          .
    |-- CMakeLists.txt
    |-- src
    |   |-- CMakeLists.txt
    |   |-- foo.c
    |   `-- foo.h
    `-- python
        |-- CMakeLists.txt
        |-- xx
            -- yy
          |   `-- __init__.py
        `-- setup.py.in
    
    1. setup.py.in
    from distutils.core import setup, Extension
    import site
    
    setup(name='Binary_name',
          packages=['xx.yy'],
          version='2.0.0',
          package_dir={ '': '${CMAKE_CURRENT_SOURCE_DIR}' },
          data_files = [(site.getsitepackages()[0] + '/xx/yy',['.so's location'])]
          )
    1. top level CMakeLists.txt link
    2. lower level CMakeLists is for your binary ========================================================================

Errors

========================================================================

  • Undefined reference: you cannot find the function definition. So you need other cpp files.
  • Your Library file cannot file header file: change target_link_libraries property to public or something. This can happen in make.

========================================================================

Catkin Build Tools

========================================================================

  • Catkin build:
    • Features

      • build-time cross-talk?
        • No vars from other packages. Each package's build space is isolated
      • Can build plain CMake packages, if they have package.xml with tag
      • No need to depend on other packages for ROS messages
      • All dependencies are built before the current package.
    • migrating from catkin_make to catkin_build:

      • find_package was not necessary for catkin_mane if packages are in the same ws
    • Advantages

      • Robust to config changes, since each pkg is isolated
      • Faster, parallel
      • can use catkin clean, instead of rm -rf
      • Can be called anywhere, not just top level
      • catkin_make --only-pkg-with-deps <target_pkg>
        • build one pkg with deps
        • But this will set a persistent env var, which makes future builds only build the current pkg.
        • So you have to use catkin_make -DCATKIN_WHITELIST_PACKAGES="" to switch back
        • catkin build doesn't have such a problem
⚠️ **GitHub.com Fallback** ⚠️