Difference between revisions of "Programming/Tools/CMake"
Line 355: | Line 355: | ||
</pre> | </pre> | ||
We haven't yet created <tt>sphere.h</tt>. Let us first | We are getting this error because we haven't yet created <tt>sphere.h</tt>. Let us first | ||
<pre> | <pre> | ||
$ mkdir .../my_project/include | $ mkdir .../my_project/include | ||
Line 361: | Line 361: | ||
and underneath it create <tt>sphere.h</tt> with the following contents: | and underneath it create <tt>sphere.h</tt> with the following contents: | ||
<pre> | <pre> | ||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#define PI 3.14159265 | |||
float get_value(void) | |||
{ | |||
float x; | |||
scanf("%f", &x); | |||
return x; | |||
} | |||
void put_value(float x) | |||
{ | |||
printf("%f\n", x); | |||
} | |||
static float my_pow(float x, int n); | |||
float surface_area(float r) | |||
{ | |||
return 4. * PI * my_pow(r, 2); | |||
} | |||
float volume(float r) | |||
{ | |||
return 4. * PI * my_pow(r, 3) / 3.; | |||
} | |||
static float my_pow(float x, int n) | |||
{ | |||
if (n < 0) | |||
return 1. / my_pow(x, -n); | |||
else if (n == 0) | |||
return 1; | |||
else | |||
return x * my_pow(x, n - 1); | |||
} | |||
</pre> | </pre> | ||
Line 374: | Line 413: | ||
(Note that we have added <tt>include_directories</tt>.) | (Note that we have added <tt>include_directories</tt>.) | ||
A library is called '''header-only''' if the full definitions of all macros, functions, and classes comprising the library are visible to the compiler in a header file form. Header-only libraries do not need to be separately compiled, packaged, and installed in order to be used. All that is required is to point the compiler at the location of the headers, and then <tt>#include</tt> the header files into the application source. Another advantage is that the compiler's optimizer can do a much better job when all the library's source code is available. | Make sure that we are under <tt>.../my_project/build/</tt> and run | ||
<pre> | |||
$ cmake .. | |||
-- *** Building my_project from /home/ubuntu/my_project *** | |||
-- Configuring done | |||
-- Generating done | |||
-- Build files have been written to: /home/ubuntu/my_project/build | |||
$ make | |||
Scanning dependencies of target bin_main | |||
[ 50%] Building C object src/CMakeFiles/bin_main.dir/main.c.o | |||
[100%] Linking C executable bin_main | |||
[100%] Built target bin_main | |||
</pre> | |||
Looks like our build has been successful. Let's test the executable: | |||
<pre> | |||
$ src/bin_main | |||
Input the radius of the sphere: 3.5 | |||
Surface area = 153.938034 | |||
Volume of sphere = 179.594376 | |||
</pre> | |||
<tt>sphere.h</tt> constitutes a "header-only" library. A library is called '''header-only''' if the full definitions of all macros, functions, and classes comprising the library are visible to the compiler in a header file form. Header-only libraries do not need to be separately compiled, packaged, and installed in order to be used. All that is required is to point the compiler at the location of the headers, and then <tt>#include</tt> the header files into the application source. Another advantage is that the compiler's optimizer can do a much better job when all the library's source code is available. | |||
The disadvantages include: | The disadvantages include: |
Revision as of 23:33, 22 December 2020
Overview
CMake is an open-source, cross-platform family of tools designed to build, test, and package software. CMake is used to control the software compilation process using simple platform and compiler independent configuration files, and generate native makefiles and workspaces that can be used in the compiler environment of your choice. The suite of CMake tools were created by Kitware in response to the need for a powerful, cross-platform build environment for open-source projects such as ITK and VTK.
CMake is part of Kitware's collection of commercially supported open-source platforms for software development.
This tutorial
In this tutorial we demonstrate how to set up and build an (admittedly very simple) project using CMake on an Ubuntu 20.04.1 LTS system. The tutorial should be applicable to other Linux systems, possibly with minor modifications.
Preparation
First, we need to install CMake:
$ sudo apt update ... $ sudo apt install cmake
Since we'll be using CMake with gcc—GNU project C and C++ compiler,— we also need to install gcc:
$ sudo apt install g++
Basic project
We'll start working from the home directory, so we
$ cd
to it. We then create a directory for the project
$ mkdir my_project
cd into it with
$ cd my_project
Now let us create a directory for source files
$ mkdir src
and the file CMakeLists.txt:
$ touch CMakeLists.txt
CMakeLists.txt contains a set of directives and instructions describing the project's source files and targets (executable, library, or both).
project(cmake_test_project) cmake_minimum_required(VERSION 3.0)
While you can build the project from the current directory (.../my_project/), CMake will generate some files that will pollute it. Therefore it is a good idea to
$ mkdir build $ cd build/ $ cmake ..
We should see something like this:
-- The CXX compiler identification is GNU 9.3.0 -- Check for working CXX compiler: /usr/bin/c++ -- Check for working CXX compiler: /usr/bin/c++ -- works -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Detecting CXX compile features -- Detecting CXX compile features - done -- Configuring done -- Generating done -- Build files have been written to: /home/ubuntu/my_project/build
Under .../my_project/build/ you will see a pretty large Makefile containing something like this:
# CMAKE generated file: DO NOT EDIT! # Generated by "Unix Makefiles" Generator, CMake Version 3.16 # Default target executed when no arguments are given to make. default_target: all .PHONY : default_target # Allow only one "make -f Makefile2" at a time, but pass parallelism. .NOTPARALLEL: #============================================================================= # Special targets provided by cmake. # Disable implicit rules so canonical targets will work. .SUFFIXES: # Remove some rules from gmake that .SUFFIXES does not remove. SUFFIXES = .SUFFIXES: .hpux_make_needs_suffix_list # Suppress display of executed commands. $(VERBOSE).SILENT: # A target that is always out of date. cmake_force: .PHONY : cmake_force #============================================================================= # Set environment variables for the build. # The shell in which to execute make rules. SHELL = /bin/sh # The CMake executable. CMAKE_COMMAND = /usr/bin/cmake # The command to remove a file. RM = /usr/bin/cmake -E remove -f # Escaping for special characters. EQUALS = = # The top-level source directory on which CMake was run. CMAKE_SOURCE_DIR = /home/ubuntu/my_project # The top-level build directory on which CMake was run. CMAKE_BINARY_DIR = /home/ubuntu/my_project/build #============================================================================= # Targets provided globally by CMake. # Special rule for the target rebuild_cache rebuild_cache: @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running CMake to regenerate build system..." /usr/bin/cmake -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) .PHONY : rebuild_cache # Special rule for the target rebuild_cache rebuild_cache/fast: rebuild_cache .PHONY : rebuild_cache/fast # Special rule for the target edit_cache edit_cache: @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "No inter active CMake dialog available..." /usr/bin/cmake -E echo No\ interactive\ CMake\ dialog\ available. .PHONY : edit_cache # Special rule for the target edit_cache edit_cache/fast: edit_cache .PHONY : edit_cache/fast # The main all target all: cmake_check_build_system $(CMAKE_COMMAND) -E cmake_progress_start /home/ubuntu/my_project/build/C MakeFiles /home/ubuntu/my_project/build/CMakeFiles/progress.marks $(MAKE) -f CMakeFiles/Makefile2 all $(CMAKE_COMMAND) -E cmake_progress_start /home/ubuntu/my_project/build/C MakeFiles 0 .PHONY : all # The main clean target clean: $(MAKE) -f CMakeFiles/Makefile2 clean .PHONY : clean # The main clean target clean/fast: clean .PHONY : clean/fast # Prepare targets for installation. preinstall: all $(MAKE) -f CMakeFiles/Makefile2 preinstall .PHONY : preinstall # Prepare targets for installation. preinstall/fast: $(MAKE) -f CMakeFiles/Makefile2 preinstall .PHONY : preinstall/fast # clear depends depend: $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-bui ld-system CMakeFiles/Makefile.cmake 1 .PHONY : depend # Help Target help: @echo "The following are some of the valid targets for this Makefile:" @echo "... all (the default if no target is provided)" @echo "... clean" @echo "... depend" @echo "... rebuild_cache" @echo "... edit_cache" .PHONY : help #============================================================================= # Special targets to cleanup operation of make. # Special rule to run CMake to check the build system integrity. # No rule that depends on this can have commands that come from listfiles # because they might be regenerated. cmake_check_build_system: $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-bui ld-system CMakeFiles/Makefile.cmake 0 .PHONY : cmake_check_build_system
However, this Makefile doesn't do anything useful. That is because we haven't written any code in our project. Let's add it.
First, let us modify .../my_project/CMakeLists.txt:
project(cmake_test_project) cmake_minimum_required(VERSION 3.0) message(STATUS "*** Building my_project from ${PROJECT_SOURCE_DIR} ***") add_subdirectory(${PROJECT_SOURCE_DIR}/src)
Make sure that we are under .../my_project/build and run
$ cmake ..
The following error message will appear:
-- *** Building my_project from /home/ubuntu/my_project *** CMake Error at CMakeLists.txt:6 (add_subdirectory): The source directory /home/ubuntu/my_project/src does not contain a CMakeLists.txt file. -- Configuring incomplete, errors occurred! See also "/home/ubuntu/my_project/build/CMakeFiles/CMakeOutput.log". See also "/home/ubuntu/my_project/build/CMakeFiles/CMakeError.log".
As the error suggests, the directory .../my_project/src/, which we have added to .../my_project/CMakeLists.txt must contain (another) CMakeLists.txt file. CMake has a hierarchical configuration system. For now, let us simply
$ touch ../src/CMakeLists.txt
(assuming we are still under .../my_project/build/). Thus we create an empty child CMakeLists.txt file that does nothing.
Let's try building again:
$ cmake ..
This time the build has been successful:
-- *** Building my_project from /home/ubuntu/my_project *** -- Configuring done -- Generating done -- Build files have been written to: /home/ubuntu/my_project/build
Adding an executable
However, this build has been trivial since the project contains no code. Let us add some code to it:
$ touch ../src/main.c $ nano ../src/main.c
and let's write the following content to .../my_project/src/main.c:
#include <stdio.h> int main() { printf("Hello, World!\n"); return 0; }
In order to build this file, we need to modify .../my_project/src/CMakeLists.txt:
add_executable(bin_main main.c)
This tells CMake to build an executable, bin_main out of the source files or, in this case, source file main.c.
Having made sure we are still under .../my_project/build, we again run
$ cmake ..
This has generated a Makefile. Now let's run make:
$ make
The output is
Scanning dependencies of target bin_main [ 50%] Building C object src/CMakeFiles/bin_main.dir/main.c.o [100%] Linking C executable bin_main [100%] Built target bin_main
bin_main will be under .../my_project/build/src/ (since the directory structure under .../my_project/build/ mimics that above).
We can run it (assuming we are under .../my_project/build/):
$ src/bin_main Hello, World!
Header-only libraries
Let us modify .../my_project/build/src/main.c as follows:
#include "sphere.h" int main(void) { float radius, vol; printf("Input the radius of the sphere: "); radius = get_value(); printf("Surface area = "); put_value(surface_area(radius)); vol = volume(radius); printf("Volume of sphere = "); put_value(vol); return EXIT_SUCCESS; }
From the directory .../my_project/build/, let us run
$ cmake .. -- *** Building my_project from /home/ubuntu/my_project *** -- Configuring done -- Generating done -- Build files have been written to: /home/ubuntu/my_project/build
and then
$ make Scanning dependencies of target bin_main [ 50%] Building C object src/CMakeFiles/bin_main.dir/main.c.o /home/ubuntu/my_project/src/main.c:1:10: fatal error: sphere.h: No such file or directory 1 | #include "sphere.h" | ^~~~~~~~~~ compilation terminated. make[2]: *** [src/CMakeFiles/bin_main.dir/build.make:63: src/CMakeFiles/bin_main.dir/main.c.o] Error 1 make[1]: *** [CMakeFiles/Makefile2:94: src/CMakeFiles/bin_main.dir/all] Error 2 make: *** [Makefile:84: all] Error 2
We are getting this error because we haven't yet created sphere.h. Let us first
$ mkdir .../my_project/include
and underneath it create sphere.h with the following contents:
#include <stdio.h> #include <stdlib.h> #define PI 3.14159265 float get_value(void) { float x; scanf("%f", &x); return x; } void put_value(float x) { printf("%f\n", x); } static float my_pow(float x, int n); float surface_area(float r) { return 4. * PI * my_pow(r, 2); } float volume(float r) { return 4. * PI * my_pow(r, 3) / 3.; } static float my_pow(float x, int n) { if (n < 0) return 1. / my_pow(x, -n); else if (n == 0) return 1; else return x * my_pow(x, n - 1); }
We need to tell the compiler about this file (actually, the directory that contains it) by modifying .../my_project/CMakeLists.txt as follows:
project(cmake_test_project) cmake_minimum_required(VERSION 3.0) message(STATUS "*** Building my_project from ${PROJECT_SOURCE_DIR} ***") include_directories(${PROJECT_SOURCE_DIR}/include) add_subdirectory(${PROJECT_SOURCE_DIR}/src)
(Note that we have added include_directories.)
Make sure that we are under .../my_project/build/ and run
$ cmake .. -- *** Building my_project from /home/ubuntu/my_project *** -- Configuring done -- Generating done -- Build files have been written to: /home/ubuntu/my_project/build $ make Scanning dependencies of target bin_main [ 50%] Building C object src/CMakeFiles/bin_main.dir/main.c.o [100%] Linking C executable bin_main [100%] Built target bin_main
Looks like our build has been successful. Let's test the executable:
$ src/bin_main Input the radius of the sphere: 3.5 Surface area = 153.938034 Volume of sphere = 179.594376
sphere.h constitutes a "header-only" library. A library is called header-only if the full definitions of all macros, functions, and classes comprising the library are visible to the compiler in a header file form. Header-only libraries do not need to be separately compiled, packaged, and installed in order to be used. All that is required is to point the compiler at the location of the headers, and then #include the header files into the application source. Another advantage is that the compiler's optimizer can do a much better job when all the library's source code is available.
The disadvantages include:
- brittleness—most changes to the library will require recompilation of all compilation units using that library;
- longer compilation times—the compilation unit must see the implementation of all components in the included files, rather than just their interfaces;
- code-bloat (this may be disputed)—the necessary use of inline statements in non-class functions can lead to code bloat by over-inlining.
Nonetheless, the header-only form is popular because it avoids the (often much more serious) problem of packaging.
Adding a library
Let us modify .../my_project/build/src/main.c as follows:
#include "sphere.h" int main(void) { float radius, vol; printf("Input the radius of the sphere: "); radius = get_value(); printf("Surface area = "); put_value(surface_area(radius)); vol = volume(radius); printf("Volume of sphere = "); put_value(vol); return EXIT_SUCCESS; }
Let us also add .../my_project/build/src/geometry.c with the following contents:
#include "sphere.h" static float my_pow(float x, int n); float surface_area(float r) { return 4. * PI * my_pow(r, 2); } float volume(float r) { return 4. * PI * my_pow(r, 3) / 3.; } static float my_pow(float x, int n) { if (n < 0) return 1. / my_pow(x, -n); else if (n == 0) return 1; else return x * my_pow(x, n - 1); }
And let us add .../my_project/build/src/simple_io.c:
#include "sphere.h" float get_value(void) { float x; scanf("%f", &x); return x; } void put_value(float x) { printf("%f\n", x); }
These source files share an include file, "sphere.h". Let us create a directory for include files:
$ mkdir .../my_project/build/include/
And let us add .../my_project/build/include/sphere.h with the following contents:
#include <stdio.h> #include <stdlib.h> #define PI 3.14159265 float get_value(void); void put_value(float x); float surface_area(float r); float volume(float r);
Let us edit .../my_project/CMakeLists.txt:
project(cmake_test_project) cmake_minimum_required(VERSION 3.0) message(STATUS "*** Building my_project from ${PROJECT_SOURCE_DIR} ***") include_directories($PROJECT_SOURCE_DIR}/include) add_subdirectory(${PROJECT_SOURCE_DIR}/src)
Let us also edit .../my_project/src/CMakeLists.txt: