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 beSHAREDorSTATIC, 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.cpp
The 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 theCMakeLists.txtin<dir>.
.
├── CMakeLists.txt
├── answer
│ ├── CMakeLists.txt
│ ├── answer.cpp
│ └── answer.hpp
└── main.cpp
CMakeLists.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 theCMakeLists.txtin thefmtdirectory.- 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 + ComplexandComplex - ComplexComplex + doubleordouble + ComplexComplex - doubleordouble - 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:
