CMake & third-party libraries
Recap
Terminologies
- target: An output or result that the build system generates, can be an executable, a library, an object file, etc.
Functions to understand:
- add_library(<name> [<type>] <sources>...): Add a library target called- <name>to be built from the source files listed.- <type>can be- SHAREDor- STATIC, corresponding to dynamic and static library.
- <sources>are the source files to be compiled, separated by spaces. The path is relative to the current CMakeLists.txt file.
 
- target_link_libraries(<target> <libraries>...): Specify the libraries that the target depends on.
Library
Suppose the file structure is
├── CMakeLists.txt
├── answer.cpp
├── answer.hpp
└── main.cppThe content of CMakeLists.txt is
cmake_minimum_required(VERSION 3.10)
project(answer)
add_library(libanswer STATIC answer.cpp)
add_executable(answer main.cpp)
target_link_libraries(answer libanswer)This will generate a static library under the build directory, and the executable will link to this library.
Subdirectory
add_subdirectory(<dir>)
The previous example is still not good enough when we have nested subdirectories or multiple libraries.
- add_subdirectory(<dir>): Add a subdirectory to the build.
- Typically we put another CMakeLists.txtin<dir>directory.
- add_subdirectory(<dir>)tells CMake to process the- CMakeLists.txtin- <dir>.
.
├── CMakeLists.txt
├── answer
│   ├── CMakeLists.txt
│   ├── answer.cpp
│   └── answer.hpp
└── main.cppCMakeLists.txt in answer directory:
add_library(libanswer answer.cpp)
target_include_directories(libanswer PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})CMakeLists.txt in the root directory:
cmake_minimum_required(VERSION 3.10)
project(answer)
# subdirectory
add_subdirectory(answer)
add_executable(answer_app main.cpp)
target_link_libraries(answer_app libanswer)Access control
PUBLIC, PRIVATE, INTERFACE
- target_include_directories(<target> PUBLIC [dir]):- You can change PUBLICtoPRIVATEorINTERFACEto control the access.
- PUBLIC: The include directories are propagated to dependent targets.
- PRIVATE: The include directories are not propagated to dependent targets.
- INTERFACE: The include directories are only used in dependent targets, current target will not use them.
 
- You can change 
- This command takes effect during compile process.
- Normally we don’t need to care about these, just use PUBLICto propagate the include directories.
target_link_libraries(<target> <PUBLIC> <item>):
- This command takes effect during link process.
- PUBLIC,- PRIVATE,- INTERFACEcontrol whether the library is propagated to dependent targets.
Suppose A depends on B, and B depends on C.
target_link_libraries(D PUBLIC B)
target_link_libraries(A PRIVATE B)
target_link_libraries(B PUBLIC C)- Any target that links to B will automatically link to C as well.
- D links against B using PUBLIC, then D will link to C as well.
- However, A links against B using PRIVATE, then A will not link to C.
Third-party libraries
Pure header library
This is the easiest way to use third-party libraries.
- Download the library from the internet.
- Copy the header files to your project.
- Add the header files to your project.
Using add_subdirectory
Recall that add_subdirectory(<dir>) will add a subdirectory to the build and process the CMakeLists.txt in the subdirectory. This means we can download source codes from the internet and put them in a subdirectory.
Suppose we want to use fmt library, we can use git to download the source code, in your project directory run:
git clone https://github.com/fmtlib/fmt.git fmt --depth 1Then you will find a fmt directory in your project.
To use the fmt library,
- add_subdirectory(fmt): Process the- CMakeLists.txtin the- fmtdirectory.- you don’t need to worry about the CMakeLists.txtin thefmtdirectory.
 
- you don’t need to worry about the 
cmake_minimum_required(VERSION 3.10)
project(fmt_example)
add_subdirectory(fmt)
add_executable(hello_fmt main.cpp)
target_link_libraries(hello_fmt PUBLIC fmt::fmt)Here fmt::fmt is for the compiled library, another option is fmt::fmt-header-only, which is the header files only. See here for more details.
fmt library
Here are some useful cases of using fmt library:
#include <fmt/core.h>
int main() {
  fmt::print("Hello, world!\n");
}
std::string s = fmt::format("The answer is {}.", 42);
// s == "The answer is 42."
#include <vector>
#include <fmt/ranges.h>
int main() {
  std::vector<int> v = {1, 2, 3};
  fmt::print("{}\n", v);
}For more details, read this.
Assignment
Implement a Complex class that overloads +, - operators.
- Two private members: realandimaginaryof typedouble.
- Constructor that takes two double numbers, realandimaginary.- By default, realis 0 andimaginaryis 0.
 
- By default, 
- Overload +,-operators that support:- Complex + Complexand- Complex - Complex
- Complex + doubleor- double + Complex
- Complex - doubleor- double - Complex
 
Read the document and use fmt library to format the output of the complex number.
- Overload <<operator for printing the complex number.- The color of the complex number should be green.
- The format of the output should be a + bi, whereais the real part andbis the imaginary part. Two spaces between+andb.
- The precision of the real and imaginary parts should be 3.
 
- The color of the complex number should be 
Complex c1(1.1243, 2.3456);
Complex c2(3.234, 4.234);
std::cout << c1 << std::endl; // Output: 1.124 + 2.346i
std::cout << c2 << std::endl; // Output: 3.234 + 4.234i- Please use consteverywhere if you don’t change the data member or passed parameters.
- Pass by reference if you can.
- You should write complex.h,complex.cpp,main.cppandCMakeLists.txtby yourself.- Write your own test cases in main.cppto test yourComplexclass.
 
- Write your own test cases in 
- Only submit complex.h,complex.cpp,main.cppandCMakeLists.txt. i.e. delete thefmtandbuildfolder before submission.
Example test case:
Complex c1(1.1243, 2.3456);
Complex c2(3.234, 4.234);
std::cout << "c1: " << c1 << std::endl;
std::cout << "c2: " << c2 << std::endl;Expected output:
