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 beSHARED
orSTATIC
, 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.txt
in<dir>
directory. add_subdirectory(<dir>)
tells CMake to process theCMakeLists.txt
in<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
PUBLIC
toPRIVATE
orINTERFACE
to 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
PUBLIC
to propagate the include directories.
target_link_libraries(<target> <PUBLIC> <item>)
:
- This command takes effect during link process.
PUBLIC
,PRIVATE
,INTERFACE
control 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 1
Then you will find a fmt
directory in your project.
To use the fmt
library,
add_subdirectory(fmt)
: Process theCMakeLists.txt
in thefmt
directory.- you don’t need to worry about the
CMakeLists.txt
in thefmt
directory.
- 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() {
::print("Hello, world!\n");
fmt}
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};
::print("{}\n", v);
fmt}
For more details, read this.
Assignment
Implement a Complex
class that overloads +
, -
operators.
- Two private members:
real
andimaginary
of typedouble
. - Constructor that takes two double numbers,
real
andimaginary
.- By default,
real
is 0 andimaginary
is 0.
- By default,
- Overload
+
,-
operators that support:Complex + Complex
andComplex - Complex
Complex + double
ordouble + Complex
Complex - double
ordouble - 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
, wherea
is the real part andb
is 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
(1.1243, 2.3456);
Complex c1(3.234, 4.234);
Complex c2std::cout << c1 << std::endl; // Output: 1.124 + 2.346i
std::cout << c2 << std::endl; // Output: 3.234 + 4.234i
- Please use
const
everywhere 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.cpp
andCMakeLists.txt
by yourself.- Write your own test cases in
main.cpp
to test yourComplex
class.
- Write your own test cases in
- Only submit
complex.h
,complex.cpp
,main.cpp
andCMakeLists.txt
. i.e. delete thefmt
andbuild
folder before submission.
Example test case:
(1.1243, 2.3456);
Complex c1(3.234, 4.234);
Complex c2std::cout << "c1: " << c1 << std::endl;
std::cout << "c2: " << c2 << std::endl;
Expected output: