Skip to content

Quick Guide to CMake

To use pico-jxglib in a Pico SDK project, you need to edit the CMake configuration file CMakeLists.txt. You only need to add a few lines, so you can integrate it without knowing all the details, but understanding what CMake actually does will help you feel more comfortable. This page explains the basics of CMake1.

CMake generates files for build tools like make or ninja based on the contents of CMakeLists.txt. You might think you could just write a Makefile directly, but CMake conveniently handles differences in compilers and OSes, which is why many development platforms use it.

Here is the core part of a CMakeLists.txt generated by the Pico SDK template:

Core Part of CMakeLists.txt
add_executable(hoge hoge.c)

target_link_libraries(hoge
    pico_stdlib)

target_include_directories(hoge PRIVATE
    ${CMAKE_CURRENT_LIST_DIR})

Here's what each part does:

  • add_executable() declares the creation of an executable. The first argument is the target name, followed by the source files. If there are multiple source files, separate them with spaces or newlines. The target name is used as the base name for the executable (e.g., hoge.elf or hoge.uf2), and is also used as the first argument for commands starting with target_ like target_link_libraries and target_include_directories.
  • target_link_libraries() specifies libraries to link with this target.
  • target_include_directories() specifies include paths for compiling the source files. PRIVATE means the include path is only valid within this CMakeLists.txt. Other keywords include PUBLIC (valid for this and all parent CMakeLists.txt) and INTERFACE (not valid for this CMakeLists.txt, but valid for all parent CMakeLists.txt).

The variable CMAKE_CURRENT_LIST_DIR contains the directory where this CMakeLists.txt is located.

Commands can be written in a single call like this:

target_link_libraries(hoge
    pico_stdlib
    hardware_i2c)

Or split into multiple calls like this:

target_link_libraries(hoge
    pico_stdlib)

target_link_libraries(hoge
    hardware_i2c)

Up to this point, you might think of CMake as "just a slightly different way to write a classic Makefile." However, the behavior of target_link_libraries() needs some explanation.

If you look at the CMakeLists.txt file above, you might wonder where files like pico_stdlib.lib, pico_stdlib.a, or pico_stdlib.so are located. You might search in the .pico_sdk directory or your project's build directory, but you won't find such files. The pico_stdlib specified here is not a file name, but the name of a target declared with add_library() in the Pico SDK's CMakeLists.txt2.

Here is an example of the CMakeLists.txt that declares pico_stdlib (simplified for clarity):

pico-sdk/src/host/pico_stdlib/CMakeLists.txt
add_library(pico_stdlib INTERFACE)

target_sources(pico_stdlib INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}/stdlib.c
)

target_link_libraries(pico_stdlib INTERFACE
    pico_stdlib_headers
    pico_platform
    pico_time
    pico_divider
    pico_binary_info
    pico_printf
    pico_runtime
    pico_stdio
    hardware_gpio
    hardware_uart
)

The INTERFACE keyword is important. Targets declared with INTERFACE are not actually compiled themselves; instead, they pass information about source files, include paths, and linked libraries to the project that calls target_link_libraries().

In the file above, pico_stdlib links to other libraries like pico_stdlib_headers and pico_platform, which are also declared as INTERFACE. This means all the information about source files and so on is passed up to the original project3.

Because INTERFACE libraries are not pre-built, the main project that calls target_link_libraries() will compile the source files that make up those libraries.

This approach has big advantages. Since the library source files are compiled in your project, you can use preprocessor macros to include only the code you need, or change behavior at compile time. This can improve performance and reduce object size.

Another important point is that include paths are automatically passed to the calling project just by using target_link_libraries(). You don't need to manually specify include paths for each library you use.

pico-jxglib follows the Pico SDK and declares its libraries as INTERFACE.


  1. CMake is a large system, and even I only know the basics described here. 

  2. If the specified target name is not found, CMake will search for an actual library file like *.lib or *.a in the library search path. 

  3. For more details, see section "2.2. Every Library is an INTERFACE Library" in the Raspberry Pi Pico-series C/C++ SDK. At first, it can be confusing if you think of a "library" as a physical file like hoge.lib or hoge.a