This CMakeLists.txt
builds an executable called main
from main.cpp
:
add_executable(main main.cpp)
This CMakeLists.txt
builds an executable called main
from main.cpp
:
add_executable(main main.cpp)
Note: Based on this post I have published buildock on GitHub.
Many C/C++ programmers and project managers know the pain of creating a reproducible build environment for all developers: Works for me is a common meme not without a reason.
My approach is to dockerize not neccessarily the application itself but the build system, encapsulating both the specific compiler version and the system around it plus all required system-level libraries in a Docker image.
Take the obligatory Hello World in C++:
// main.cpp #include <iostream> using namespace std; int main() { cout << "Hello, World!" << endl; return 0; }
and the corresponding Makefile
:
all: g++ -o helloworld main.cpp
How can we compile this simple project without installing a compiler and GNU make on the local computer (no cheating by using build servers allowed)?
Actually it’s rather simple:
docker run --user $(id -u):$(id -g) -v $PWD:/app -it ulikoehler/ubuntu-gcc-make make
Breaking it down:
docker run
: Create a new docker container and run a command in it--user $(id -u):$(id -g)
: This makes the docker container run with the current user’s ID and the current user’s group – both for preventing the compiler to create output files as root
and to safeguard against some IT security risks. Also see How to run docker container as current user & group-v $PWD:/app
: Mount the current directory ($PWD
) on /app
in the container. Since the Dockerfile used to build the container contains the WORKDIR /app
directive, whatever command we run in the container will by default be executed in the /app
directory – and therefore in the current local directory on the host.-it
runs the container in interactive mode, i.e. keypressed are passed down to the command you are running. Also, this means that our command will only finish when the container has finished executing.ulikoehler/ubuntu-gcc-make
: This is the image we’re using for that example. It’s nothing more than an ubuntu:18.04
base image with build-essentials
and make
installed and WORKDIR
set to /app
make
: This is the command we’ll run in the container. You can use any command here, even no command is possible (in which case the container’s default command will be used – in case of ulikoehler/ubuntu-gcc-make
that is CMD [ "/usr/bin/make" ]
)Here’s the complete Dockerfile
used to generate ulikoehler/ubuntu-gcc-make
:
FROM ubuntu:18.04 RUN apt update && apt -y install build-essential make && rm -rf /var/lib/apt/lists/* WORKDIR /app CMD [ "/usr/bin/make" ]
With automake/m4 in configure.ac
you use syntax like
AC_CHECK_FUNCS(atan2)
which defines HAVE_FDATASYNC
if the function is available.
In CMake, you can use check_symbol_exists()
for the same purpose like this:
cmake_minimum_required(VERSION 3.0) include(CheckSymbolExists) # Define executable add_executable(myexe main.c) # atan2 requires the math library to be linked list(APPEND CMAKE_REQUIRED_LIBRARIES m) check_symbol_exists(atan2 math.h HAVE_ATAN2) # Add compile definitions if we have the library if(HAVE_ATAN2) target_compile_definitions(myexe PRIVATE -DHAVE_ATAN2) endif()
Note that check_symbol_exists()
does not automatically add a preprocessor define but you have to do that manually (see the last block in the code shown above). While this might seem less comfortable at first, this approach provides you with much more flexibility on how to handle missing or available functions.
See the CMake check_symbol_exists()
documentation for more details.
cmake_minimum_required(VERSION 3.0) include(CheckSymbolExists) # Define executable add_executable(myexe main.c) # atan2 requires the math library to be linked list(APPEND CMAKE_REQUIRED_LIBRARIES m) check_symbol_exists(atan2 math.h HAVE_ATAN2) # Add compile definitions if we have the library if(HAVE_ATAN2) target_compile_definitions(myexe PRIVATE -DHAVE_ATAN2) endif()
If you have CMake code using check_symbol_exists(...)
like
list(APPEND CMAKE_REQUIRED_LIBRARIES m) check_symbol_exists(atan2 math.h HAVE_ATAN2)
but you get an error message like
CMake Error at CMakeLists.txt:8 (check_symbol_exists): Unknown CMake command "check_symbol_exists". -- Configuring incomplete, errors occurred!
you need to add
include(CheckSymbolExists)
near the top of your CMakeLists.txt
(before your first call to check_symbol_exists()
).
if
/else
/endif
looks like this in CMake:
if(myvar) message("if") else() message("else") endif()
Simply add these lines to the end of your CMakeLists.txt
and replace myTarget
by the name of your build target (usually the first argument to add_executable(...)
or add_library(...)
):
# Include boost find_package( Boost 1.30 COMPONENTS program_options REQUIRED ) target_include_directories( myTarget PRIVATE ${Boost_INCLUDE_DIR}) target_link_libraries( myTarget ${Boost_LIBRARIES} )
If you have multiple targets, copy & paste the last two lines for each target.
If you need a specific version of boost, replace 1.30
by the minimum version you need.
If you want to build an executable / library with debug symbols in CMake, run
cmake -DCMAKE_BUILD_TYPE=Debug . make
Conversely, if you want to build an executable / library in release mode, run
cmake -DCMAKE_BUILD_TYPE=Release . make
If you use SDCC to compile your C code for your microcontroller and you encounter an error message like this:
at 1: warning 119: don't know what to do with file 'main.o'. file extension unsupported
you have to configure your build system to use the .rel
suffix for object files instead of the standard .o
. SDCC expects built object files to have the .rel
extension! See How to change CMake object file suffix from default “.o” for details on how to do that in CMake.
You want to upload a package to a PPA using dput <filename>.changes
, but you see an error message like this:
Checking signature on .changes gpg: /home/uli/dev/deb-buildscripts/stm8flash_0.1-git261-deb1-1_amd64.changes: error 58: gpgme_op_verify gpgme_op_verify: GPGME: No data
You need to sign your .changes
file before uploading! Do this using
debsign -k 1BBC8BAA <filename>.changes
where 1BBC8BAA
is your GPG key ID which you intend to sign with.
In order to configure CMake to use an alternate object file suffix (default: .o
on Linux) use these lines in your CMakeLists.txt
:
set(CMAKE_C_OUTPUT_EXTENSION ".rel") set(CMAKE_CXX_OUTPUT_EXTENSION ".rel")
This example changes the output extension from .o
to .rel
(which is required for the SDCC compiler). Be sure to replace ".rel"
by your desired output suffix.
Note that in order for these to take effect, you might need to completely remove CMakeCache.txt
, CMakeFiles
& cmake_install.cmake
:
rm -rf CMakeCache.txt CMakeFiles cmake_install.cmake
If you have been looking desperately for a working CMake example for the SDCC compiler for STM8 microcontrollers here’s my take on it:
cmake_minimum_required(VERSION 3.2) set(CMAKE_C_OUTPUT_EXTENSION ".rel") set(CMAKE_C_COMPILER sdcc) set(CMAKE_SYSTEM_NAME Generic) # No linux target etc # Prevent default configuration set(CMAKE_C_FLAGS_INIT "") set(CMAKE_EXE_LINKER_FLAGS_INIT "") project(STM8Blink C) SET(CMAKE_C_FLAGS "-mstm8 --std-c99") add_executable(main.ihx main.c) # Flash targets add_custom_target(flash ALL COMMAND stm8flash -c stlink -p stm8s105c6 -w main.ihx)
This will build main.ihx
from main.c
. main.ihx
is a Intel Hex file which can be directly flashed using stm8flash.
The last lines setup make flash
; you might need to use the correct microcontroller (stm8s105c6
in this example, run stm8flash -l
to show supported devices) and the correct flash adapter (stlink
, stlinkv2
, stlinkv21
, stlinkv3
or espstlink
).
The setup example shown here is for the STM8S eval board.
I suppose it can be easily modified for other microcontrollers, but I haven’t tried that so far.
You are trying to build a software that is using the CMake build system.
You are trying to run make
to build, but you see this error message:
make: *** No targets specified and no makefile found. Stop.
Before running make
, you need to configure your build using CMake.
The simplest way of doing that is to run
cmake .
Typically you only need to do that once for each project ; CMake will automatically detect changes to CMakeLists.txt
when you run make
.
After that, you can run make
again. If the build is successful, you’ll see a message like this:
[ 50%] Building CXX object CMakeFiles/main.dir/main.cpp.o [100%] Linking CXX executable main [100%] Built target main
You want to build an application using CMake but you see an error message like this:
CMake Error at CMakeLists.txt:1: Parse error. Expected a command name, got unquoted argument with text "//". -- Configuring incomplete, errors occurred! See also "/ram/CMakeFiles/CMakeOutput.log". Makefile:176: recipe for target 'cmake_check_build_system' failed make: *** [cmake_check_build_system] Error 1
You CMakeLists.txt
likely looks like this:
// Create main executable add_executable(main main.cpp)
The issue is in the first line:
// Create main executable
CMake comments start with #
, not with //
!
Change any comment line starting with //
to start with #
instead. In our example:
# Create main executable add_executable(main main.cpp)
Then try building again (e.g. using cmake .
or make
). Unless you have other issues in your CMake configuration, the output should now look like this (for simple builds):
-- Configuring done -- Generating done -- Build files have been written to: /ram [100%] Built target main
In order to build a package using maven, run
mvn package
If you also want to install the maven package in the local maven repository (usually in ~/.m2
), use
mvn install
In case you don’t have Maven installed, you can install it on Ubuntu using
sudo apt install maven
This is the minimal CMakeLists.txt for building an executable named myproject
from main.cpp
:
cmake_minimum_required (VERSION 2.8.11) project (MyProject) add_executable (myproject main.cpp)
Note: If you are on Windows, you can not install scipy using pip! Follow this guide instead: https://www.scipy.org/install.html. This blog post is only for Linux-based systems!
When building some of my libraries on Travis, I encountered this error during
sudo pip3 install numpy scipy --upgrade
numpy.distutils.system_info.NotFoundError: No lapack/blas resources
Install lapack and blas:
sudo apt-get -y install liblapack-dev libblas-dev
In most cases you will then get this error message:
error: library dfftpack has Fortran sources but no Fortran compiler found
Fix that by
sudo apt-get install -y gfortran
In Travis, you can do it like this in .travis.yml
:
before_install: - sudo apt-get -y install liblapack-dev libblas-dev gfortran
You’re trying to compile something (e.g. using GCC) on Ubuntu, but you get an error message similar to this one:
/usr/bin/ld: error: cannot open crt1.o: No such file or directory /usr/bin/ld: error: cannot open crti.o: No such file or directory /usr/bin/ld: error: cannot open crtn.o: No such file or directory
You want to compile and install libc++ (sometimes also named libcxx), but CMake complains with this error message
CMake Error at cmake/Modules/MacroEnsureOutOfSourceBuild.cmake:7 (message):
libcxx requires an out of source build. Please create a separate</em>
build directory and run 'cmake /path/to/libcxx [options]' there.
Call Stack (most recent call first):
CMakeLists.txt:24 (MACRO_ENSURE_OUT_OF_SOURCE_BUILD)
CMake Error at cmake/Modules/MacroEnsureOutOfSourceBuild.cmake:8 (message):
In-source builds are not allowed.
CMake would overwrite the makefiles distributed with Compiler-RT.
Please create a directory and run cmake from there, passing the path
to this source directory as the last argument.
This process created the file `CMakeCache.txt' and the directory `CMakeFiles'.
Please delete them.
Call Stack (most recent call first):
CMakeLists.txt:24 (MACRO_ENSURE_OUT_OF_SOURCE_BUILD)