Using CMake to Build Your Own Project
Now that you know how to use CMake to build a project, we can now peel back a layer and look at how to use the CMake language to set up a project. This article will walk though the basics of building a simple example project. This guide is not a complete guide, but instead is intended to present things in a logical way that help set you on the right path for learning CMake. There will also be some areas that are key to CMake that are not covered in detail in this section. We encourage those curious to consult the documentation or other online resources to get more details (and come back with an article suggestion!).
Getting Started - The CMakeLists.txt Files
CMake code is written through the use of a file called CMakeLists.txt. Instead of having a specific extension (like .c or .py), all CMake code for a project is in file(s) with this name. You are allowed to have multiple CMake files, but they must be in different directories; you cannot have two CMakeLists.txt in the same directory. The "main" CMake file is often the one with the project command in it, and the path to the "main" file is what you provide to the cmake command to configure and build the project. Often times the path is just .. since common practice is to have the main CMakeLists.txt file in the top level of the project, and then to have a build folder where you build the project.
As you setup your project and organize the code into folders, it is also a good idea to consider how you want to do the CMake setup. A common approach is to create one CMakeLists.txt file per directory (assuming there is CMake code to be carried out in that directory) so that the CMake code relating specific actions, files, or directories is executed near the resource CMake needs to interact with. For smaller projects, you may simply just want to keep everything in a single CMakeLists.txt file, but this example will focus on the former design.
With this in mind, we will have the following setup for this example:
include/
- my_api.h
- CMakeLists.txt
source/
- secrets/
- my_secrets.h
- CMakeLists.txt
- my_helpers.h
- my_source_file.cpp
- my_source_file2.c
- CMakeLists.txt
CMakeLists.txt
and can now being filling out the main
CMakeLists.txt for our project!
Setting up a project
The main CMakeLists.txt file is where you want setup key parts of your project. This includes things like: finding external libraries, establishing project build options, setting compiler options and flags, and preparing the source files. More complex options, such as preparing input files or running other scripts, are also possible with CMake, but are not covered in this example.
For most actions in CMake, every line will be some sort of action followed by parameters in parenthesis, including setting variables. From there, parameters should be in the order that the API expects them (like calls in C or C++), though technically named parameters may be out of order (like python). Since your editor of choice may not highlight named parameters, it usually better to stick with the order that you see in other examples or the documentation online. For these named parameters, instead of doing parameter_name=value like Python, it must be separated with a space, and generally in all caps: PARAMETER_NAME value.
Starting with the top of the main CMakeLists.txt, one can usually expect to find these two lines:
cmake_minimum_required(VERSION 3.17 FATAL_ERROR)
# Establish the project!
project(FOO VERSION 1.0.2 LANGUAGES C CXX)
The first line tells CMake what version of CMake is needed to support this project. If you don't know what version you need, start with the version on the system you will be running on (cmake --version), and go from there. In this particular example, the line is telling CMake that it needs at least version 3.17 to run. The call does offer the ability to set a maximum supported CMake version, but you will not find this common. Documentation for all CMake calls can be found on the cmake.org website, and the documentation offers a simple drop down menu to change the documentation version. For example, here is the documentation for cmake_minimum_required.
The second command is where the project begins to come alive. Here, the code is telling CMake the name of the whole project (which is NOT a target, explained later), what "version" it is, and what languages it can expect to find and and needs to grab compilers for. Take care to only call the command once, as doing it multiple times will reset internal variables and may cause odd CMake bugs. Calling it multiple times is allowed, and does have a purpose, but simple projects will likely never need to do this.
Finally, comments in CMake are done with the # symbol.
Preparing the Main Library (or Executable)
This example will focus on building the main bit of code for a project. For libraries, that will look something like this:
add_library(my_library "")
And for executables, it will look like this:
add_executable(my_exec "")
Both calls follow the same pattern here. The first parameter is the name of the CMake target to associate with the library/executable. This target is essentially a variable that can be used for other CMake calls to influence how it is built (for example, setting certain compile flags or adding source files to it). The second parameter is where you can list out source files for the target to include in the building process. Here, and the in the rest of the example, it will be left as the "" so we can add them later once we know which files to add. If a project wishes to add the source files here, they can do so either by typing them all out manually (with the full path to the files, and each file separated with a space), or with a variable that has all of the files listed. This style is not presented here, as this example will focus on adding the files later on.
Traversing Subdirectories
Since the setup of this example has multiple CMake files scattered through the directory structure, the project needs some way to tell CMake that there are more files to consider. To do this, the command add_subdirectory is used as follows:
add_subdirectory(source)
Here, this command tells CMake to look into the source folder and look for a CMakeLists.txt. If that file is not found in that directory, CMake will stop with an error message indicating this.
Adding Source Files
Now that the example project has included the source folder, we can add in the CMake code necessary to include our source files. The command to do this, target_sources, is fairly straightforward. This command takes the path to the file(or multiple files) to be considered. While not shown in this example, some CMake projects put multiple files into a single CMake variable, and then give the variable to this command.
# "my_library" is the target from the "add_XXXX" command earlier!
target_sources(my_library PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/my_source_file.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/my_source_file2.c"
)
In this snippet, we are adding files to our my_library target. The PRIVATE scope keyword generally aligns with the concept of PRIVATE versus PUBLIC used in C++, but is not elaborated on in this example. While this tag may not be necessary in this command (and others that also have the tag), note that errors can occur if some commands have the tag and other commands do not. As such, it is generally better to specify a scope than not.
The last part of the command is adding all the files we want CMake to consider for compilation. Here the example chooses to make use of the CMake variable CMAKE_CURRENT_SOURCE_DIR to avoid having to type out the full hard-coded path. This variable is set (by CMake) to the directory containing the current CMakeLists.txt file. This particular example chooses to add in one C++ source file and one C source file (which CMake determines based on file extension).
The use of the CMake variable also teases the syntax for using variables in CMake (the $, and {} surrounding the variable name), which is similar to using variables in other scripting languages (such as Bash). Both user created variable and variables provide by CMake can be accessed using this syntax.
Testing Everything Out So Far -- Build Attempt #1
Now that the example has a project setup, a simple target setup, and some source files to compile with the target, let's try to compile a "hello world" example. Note that this example is building a library, so there is no main function.
For reference, here are the files being tested (using the file structure outline at the beginning of the article)
:
Top level CMakeLists.txt:
cmake_minimum_required(VERSION 3.17 FATAL_ERROR)
project(FOO VERSION 1.0.2 LANGUAGES C CXX)
add_library(my_library "")
add_subdirectory(source)
In
source/CMakeLists.txt:# "my_library" is the target from the "add_XXXX" command earlier!
target_sources(my_library PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/my_source_file.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/my_source_file2.c"
)
In
source/my_source_file.cpp:#include <iostream>
void a_helper_funciton()
{
std::cout << "Hello, " << std::endl;
}
In
source/my_source_file2.c:#include <stdio.h>
int a_second_helper()
{
printf("World!\n");
}
And following the build process outlined in the previous, we can see that we have a minimal working example!
mkdir build
cd build/
cmake ..
-- The C compiler identification is GNU 13.3.0
-- The CXX compiler identification is GNU 13.3.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (1.9s)
-- Generating done (0.0s)
-- Build files have been written to: <>/build
make
[ 33%] Building CXX object CMakeFiles/my_library.dir/source/my_source_file.cpp.o
[ 66%] Building C object CMakeFiles/my_library.dir/source/my_source_file2.c.o
[100%] Linking CXX static library libmy_library.a
[100%] Built target my_library
ls
CMakeCache.txt CMakeFiles Makefile cmake_install.cmake libmy_library.a source
As expected, cmake found a C and C++ compiler, and used them to compile the two source files outlined in the
source/CMakeLists.txt file. Note that CMake used our target name to build the library file, libmy_library. It also built a static library since neither the my_library target, nor the global CMake options specified which type of library to build. To explicitly build libmy_library as a shared library, change the library creation line to:add_library(my_library SHARED "")
For executables, after finishing the build, you would have the final executable that can be run, and it's name would match the target given to
add_executable.
Adding in Headers
So far, the example has only focused on a very simple setup that is not practical for most projects. For starters, its very likely that a project will have its code split up into header and source files, will depend on third party libraries (such as MPI), may need special build considerations, and may want to install public headers. This section expands the example to demonstrate how to include header files in the overall build process.
Adding in Internal Headers
Adding headers into CMake depends on the location of where the header is, and how the code is setup. If the the source code can find the files on its own, then there is nothing that needs to be done in CMake to assist. For example, assume the header file, source/my_helpers.h, contains the following:
const int secret_value = 42;
the source file,
source/my_source_file.cpp, contains:#include "my_helpers.h"
#include <iostream>
void a_helper_function()
{
std::cout << "Hello, " << secret_value << std::endl;
}
and the source file,
source/my_source_file2.c, contains:#include "my_helpers.h"
#include <stdio.h>
int a_second_helper()
{
printf("World! %d\n", secret_value);
}
In this specific setup, there is no change needed in the CMake code, as the files are in the same directory as the source file. Indeed, when we build and compile the code, there are not any compile errors about missing headers:
cmake ..
-- The C compiler identification is GNU 13.3.0
-- The CXX compiler identification is GNU 13.3.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (1.5s)
-- Generating done (0.0s)
-- Build files have been written to: <>/build
make
[ 33%] Building CXX object CMakeFiles/my_library.dir/source/my_source_file.cpp.o
[ 66%] Building C object CMakeFiles/my_library.dir/source/my_source_file2.c.o
[100%] Linking CXX shared library libmy_library.so
[100%] Built target my_library
However, when a project needs to include a header in a location that a compiler cannot find by default, the exact CMake code depends on how the example is setup. Consider these changes to
source/secrets/my_secrets.h,const int super_secret_value = 67;
these changes to
source/my_source_file.cpp,#include "my_helpers.h"
#include "secrets/my_secrets.h"
#include <iostream>
void a_helper_function()
{
std::cout << "Hello, " << secret_value << std::endl;
std::cout << "Hello, " << super_secret_value << std::endl;
}
and these changes to
source/my_source_file2.c:#include "my_helpers.h"
#include "my_secrets.h"
#include <stdio.h>
int a_second_helper()
{
printf("World! %d\n", secret_value);
printf("World! %d\n", super_secret_value);
}
When this setup is built, the compiler is able to build one file, but not the other:
make
[ 33%] Building CXX object CMakeFiles/my_library.dir/source/my_source_file.cpp.o
[ 66%] Building C object CMakeFiles/my_library.dir/source/my_source_file2.c.o
<>/source/my_source_file2.c:2:10: fatal error: my_secrets.h: No such file or directory
2 | #include "my_secrets.h"
| ^~~~~~~~~~~~~~
compilation terminated.
make[2]: *** [CMakeFiles/my_library.dir/build.make:90: CMakeFiles/my_library.dir/source/my_source_file2.c.o] Error 1
make[1]: *** [CMakeFiles/Makefile2:100: CMakeFiles/my_library.dir/all] Error 2
make: *** [Makefile:91: all] Error 2
This is because in the C++ file, the second header is listed as a full path, but the in the C file, only the header name is listed. To specifically tell CMake it needs to pass additional include paths to all files being compiled, the command
target_include_directories is available. In this example, this command is added to the source/CMakeLists.txt file:target_include_directories(my_library PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/secrets)
Another valid option is to create a
CMakeLists.txt file in source/secrets directory, and add the code there:# In source/CMakeLists.txt:
add_subdirectory(secrets)
# In source/secrets/CMakeLists.txt:
target_include_directories(my_library PRIVATE
${CMAKE_CURRENT_SOURCE_DIR})
Regardless of the location of the command, the key detail in ensuring headers are properly included by the code is double checking the paths used by the source files, the CMake files, and ultimately the paths provided to the compiler:
# Prior setup omitted
make VERBOSE=1
# Some verbose "make" output omitted
[ 33%] Building CXX object CMakeFiles/my_library.dir/source/my_source_file.cpp.o
/usr/bin/c++ -Dmy_library_EXPORTS -I<project path>/source/secrets -fPIC -MD -MT CMakeFiles/my_library.dir/source/my_source_file.cpp.o -MF CMakeFiles/my_library.dir/source/my_source_file.cpp.o.d -o CMakeFiles/my_library.dir/source/my_source_file.cpp.o -c <project path>/source/my_source_file.cpp
[ 66%] Building C object CMakeFiles/my_library.dir/source/my_source_file2.c.o
/usr/bin/cc -Dmy_library_EXPORTS -I<project path>/secrets -fPIC -MD -MT CMakeFiles/my_library.dir/source/my_source_file2.c.o -MF CMakeFiles/my_library.dir/source/my_source_file2.c.o.d -o CMakeFiles/my_library.dir/source/my_source_file2.c.o -c <project path>/source/my_source_file2.c
[100%] Linking CXX shared library libmy_library.so
# Final output omitted.
As expected, the CMake command is successfully passing an include path to the compiler
-I<project path>/source/secrets so that it includes the secret directory.
Using a the full path (including .. traversal) in the source code versus doing it in CMake is ultimately up to the project designer, but if the paths are long, doing it CMake can help make the code cleaner, though potentially at the cost of obscuring where the headers are located.
Using and Setting the Public Header
So far, the headers being used are specific to the internal library itself. For projects intending to install a library, there is also often the need to provide a public header. CMake provides the means to tweak attributes about many things, including the public header for a library:
set_target_properties(my_library PROPERTIES PUBLIC_HEADER include/my_api.h)
But just like before, this does not tell CMake that this header also needs to be on the include path passed to the compiler, so for any target that needs it, be sure to add the following code to the appropriate directory:
target_include_directories(my_library PRIVATE
${CMAKE_CURRENT_SOURCE_DIR})
Getting More Involved in the CMake Process
Most projects are a bit more complex than a simple library with a few headers. This next section covers a few more CMake concepts that should cover most of the concepts needed to build a simple, portable research library.
Controlling the Build Process with CMake Options
Once a project starts to get past the early stages, there is often a need to have toggles for controlling how the project is built. CMake offers a fairly simple way for offering different build options. The simplest way to set one up is to use the option command:
option(MY_OPTION_NAME "Helpful string text description" ON)
This command creates a boolean variable (called
MY_OPTION_NAME in this example), and defaults it to "ON" (or true). The default value is used when the command is not manually passed to cmake or adjusted with ccmake. To set the option to off with the command line, -DMY_OPTION_NAME=OFF would need to be passed.
In addition to user-based options, sometimes a project needs specific settings from the compiler. For example, to set the C++ standard that CMake should request when compiling:
# Can also be passed via command line too!
set(CMAKE_CXX_STANDARD 23)
Or to add compile flags to a specific target:
target_compile_options(my_library PRIVATE -Wall -Wextra -Wpedantic)
Printing Out Messages
Like most programming languages, being able to output text to the screen is helpful both for debugging errors and providing feedback and information to the user. CMake provides this capability through the message command.
message(STATUS "String of what we want to print out, such as a variable value: ${MY_OPTION_NAME}")
The above command will print out the string between the quotes, and like other shell scripting languages, replace the
${MY_OPTION_NAME} with the value of the associated variable. If the variable does not exist, CMake will not error out (or tell you) and will simply just print a empty string.
While the above command used the STATUS option to do a normal print out, there is also the FATAL_ERROR option to stop CMake from continuing on. Further documentation on other options can be found here.
Using Conditional Logic
Another key feature of programming languages is being able to do branching paths based on a condition. And without this, the option command from above would not be super useful. To do an if-statement in CMake, the following syntax is used:
if(<condition>)
# Code to do if condition is true
elseif(<condition 2>)
# Code to do if condition2 is true
else()
# Code to do in all other cases
endif()
Detailing the rules and limitations of how to build conditional statements is out of the scope for this document, but a few examples of common idioms are below. For more details on the exact rules, please see the documentation for the if command here.
if(MY_OPTION_NAME)
message(STATUS "My option is indeed set!")
endif()
# Common logic operators (NOT, OR, AND) are provided as well
if(NOT CMAKE_BUILD_TYPE)
message(STATUS "No CMAKE_BUILD_TYPE specified!")
endif()
Adding in MPI (and other common libraries)
HPC projects also often depend on other external libraries as well, such as MPI or a threading library (details on building GPU codes with CMake are in the next article). To tell CMake to look for (but not necessarily use) a particular library, one can do:
find_package(MPI REQUIRED)
find_package(Threads REQUIRED)
With this command, the first parameter lists the package that CMake will look for, followed by any other options about that package. For simplicity's sake, only the option to tell CMake that it should fail if it cannot find the package (the
REQUIRED clause) is shown. The there are other options to look for specific versions or other components, but they are out the scope of this example. For details on how to make find_package(MyCustomLibrary) work, please see the next topic.
CMake will provide an error if it does not know how to find a specific package, but finding a package is just the first step. To be able to make CMake to link to the package in the appropriate places, we need to introduce the target_link_libraries command. This command tells CMake that for a given target, it should link it together with the other targets or libraries passed in. For example, to link with MPI and a threading library, one might might add the following CMake code their project:
target_link_libraries(my_library PRIVATE MPI::MPI_C Threads::Threads)
Generally the names of the targets to use for linking are fairly close to the name of the name passed to
find_package, but it's best to consult the documentation for that package to make sure the correct target is used. Additionally, the use of find_package also brings in the appropriate headers. This allows the C/C++ code to include mpi.h without the CMake project having to explicitly specify to CMake to include an additional header.
Setting up the Install Command
Up to this point, the example library can be built, but not installed. Indeed, if make install is run, the make command reveals that there is not an install target, and stops. To make this command available, we need to tell CMake that a target can be installed via the install command:
install(TARGETS my_library)
Like all commands shown in this example, this command is shown in its simplest use case and offers more flexibility than shown. For full details on the command, including on how to specify where and what is installed, please see the documentation for install here.
The Full Example
To conclude this article, the example with all of the topics covered will be listed below. The example follows the file structure outlined at the start of the article, and all relevant command output from the build process is shown.
Files
The files as used in the example as listed below.
Top level CMakeLists.txt:
cmake_minimum_required(VERSION 3.17 FATAL_ERROR)
project(FOO VERSION 1.0.2 LANGUAGES C CXX)
option(ADD_WARNINGS "Add warnings to compilation" OFF)
add_library(my_library SHARED "")
set_target_properties(my_library PROPERTIES PUBLIC_HEADER include/my_api.h)
find_package(MPI REQUIRED)
target_link_libraries(my_library MPI::MPI_CXX)
message(STATUS "Are we using warnings: ${ADD_WARNINGS}")
if(ADD_WARNINGS)
target_compile_options(my_library PRIVATE -Wall -Wextra -Wpedantic)
endif()
add_subdirectory(include)
add_subdirectory(source)
install(TARGETS my_library)
In include/CMakeLists.txt:
target_include_directories(my_library PRIVATE
${CMAKE_CURRENT_SOURCE_DIR})
And in
include/my_api.h:#ifndef MY_API_H
#define MY_API_H
#ifdef __cplusplus
extern "C" {
#endif
void a_helper_funciton();
int a_second_helper();
#ifdef __cplusplus
}
#endif
#endif
In source/CMakeLists.txt:
target_include_directories(my_library PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/secrets)
target_sources(my_library PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/my_source_file.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/my_source_file2.c"
)
In source/my_helpers.h:
const int secret_value = 42;
In source/my_source_file.cpp:
#include "my_api.h"
#include "my_helpers.h"
#include "secrets/my_secrets.h"
#include <mpi.h>
#include <iostream>
void a_helper_funciton()
{
std::cout << "Hello, " << secret_value << std::endl;
std::cout << "Hello, " << super_secret_value << std::endl;
int my_rank;
MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
std::cout << my_rank << std::endl;
}
In
source/my_source_file2.c:#include "my_api.h"
#include "my_helpers.h"
#include "my_secrets.h"
#include "mpi.h"
#include <stdio.h>
int a_second_helper()
{
printf("World! %d\n", secret_value);
printf("World! %d\n", super_secret_value);
int my_size;
MPI_Comm_size(MPI_COMM_WORLD, &my_size);
printf("%d\n", my_size);
}
In
source/secrets/my_secrets.h:const int super_secret_value=67;
Note that
source/secrets/CMakeLists.txt is not used in this version of the example project.
Output from Configuring and Building
To show that our options affect the build, this section will provide the output from two different configurations. The first configuration is from cmake -DCMAKE_INSTALL_PREFIX=<path A> ..:
cmake -DCMAKE_INSTALL_PREFIX=<path A> ..
-- The C compiler identification is GNU 13.3.0
-- The CXX compiler identification is GNU 13.3.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found MPI_C: <path B>/openmpi/4.1.5/lib/libmpi.so (found version "3.1")
-- Found MPI_CXX: <path B>/openmpi/4.1.5/lib/libmpi.so (found version "3.1")
-- Found MPI: TRUE (found version "3.1")
-- Are we using warnings: OFF
-- Configuring done (3.1s)
-- Generating done (0.0s)
-- Build files have been written to: <path C>/build
make
[ 33%] Building CXX object CMakeFiles/my_library.dir/source/my_source_file.cpp.o
[ 66%] Building C object CMakeFiles/my_library.dir/source/my_source_file2.c.o
[100%] Linking CXX shared library libmy_library.so
[100%] Built target my_library
make install
[100%] Built target my_library
Install the project...
-- Install configuration: ""
-- Installing: <path A>/lib/libmy_library.so
-- Set non-toolchain portion of runtime path of "<path A>/lib/libmy_library.so" to ""
-- Installing: <path A>/include/my_api.h
Where
Path A is the path given to the cmake command, Path B is the location of the MPI installation found by CMake, and Path C is the location of where the cmake command was run.
The second configuration utilizes the introduced ADD_WARNINGS flag like so:
cmake -DCMAKE_INSTALL_PREFIX=<path A> -DADD_WARNINGS=ON ..
-- The C compiler identification is GNU 13.3.0
-- The CXX compiler identification is GNU 13.3.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found MPI_C: <path B>/openmpi/4.1.5/lib/libmpi.so (found version "3.1")
-- Found MPI_CXX: <path B>/openmpi/4.1.5/lib/libmpi.so (found version "3.1")
-- Found MPI: TRUE (found version "3.1")
-- Are we using warnings: ON
-- Configuring done (3.0s)
-- Generating done (0.0s)
-- Build files have been written to: <path C>/build
make
[ 33%] Building CXX object CMakeFiles/my_library.dir/source/my_source_file.cpp.o
[ 66%] Building C object CMakeFiles/my_library.dir/source/my_source_file2.c.o
<path C>/source/my_source_file2.c: In function ‘a_second_helper’:
<path C>/source/my_source_file2.c:16:1: warning: control reaches end of non-void function [-Wreturn-type]
16 | }
| ^
[100%] Linking CXX shared library libmy_library.so
[100%] Built target my_library
make install
[100%] Built target my_library
Install the project...
-- Install configuration: ""
-- Installing: <path A>/lib/libmy_library.so
-- Set non-toolchain portion of runtime path of "<path A>/lib/libmy_library.so" to ""
-- Up-to-date: <path A>/include/my_api.h
Where Path A is the path given to the cmake command, Path B is the location of the MPI installation found by CMake, and Path C is the location of where the cmake command was run. Notice this time that our CMake message shows that we are using the option we provided, confirmed by the warning produced by the compiler as it compiled the files. Lastly, notice that the header was not installed, as CMake determined that it was the same as what was already there (from running the previous configuration).