CMake2
Recap
Why multiple files?
It’s convenient to put all codes in one file, but it has some disadvantages:
- All the codes are mixed together, making it hard to read and maintain.
- When your project becomes larger, the time to compile the code becomes longer.
- Even if you only modify one file, you have to compile the whole project.
How to separate codes into multiple files?
Before: one source file.
// main.cpp
#include <iostream>
void print_hello() {
std::cout << "Hello, world!" << std::endl;
}
int main() {
();
print_helloreturn 0;
}
After: 1 header file, 2 source files.
// hello.h
#pragma once
void print_hello();
// hello.cpp
#include "hello.h"
#include <iostream>
void print_hello() {
std::cout << "Hello, world!" << std::endl;
}
// main.cpp
#include "hello.h"
int main() {
();
print_helloreturn 0;
}
After we separate the codes into multiple files, we can compile the project faster.
- If we only modify
hello.cpp
, we only need to compilehello.cpp
. - If we modify
main.cpp
, we only need to compilemain.cpp
. - If we modify the function prototype in
hello.h
, we have to recompile all the source files that includehello.h
.- In our case, we have to recompile both
hello.cpp
andmain.cpp
.
- In our case, we have to recompile both
How to compile multiple files?
Use g++
as example, we can compile multiple files by:
Compile the source files into object files.
g++ -c hello.cpp -o hello.o g++ -c main.cpp -o main.o
Link the object files into an executable file.
g++ hello.o main.o -o main
Makefile
Pros:
- As long as you define the rules in Makefile, it will automatically compile the project.
- Make will atuomatically decides which files to recompile by checking the dependencies.
Cons:
- Makefile requires you to write rules manually.
- Different compilers may have different syntax, you can’t use one Makefile for different compilers.
CMake
CMake is a build system generator, it generates native build scripts for various compilers.
- CMake is platform-independent, you can use one CMake project for different operating systems and different compilers.
- CMake can detect the relationship between source files and header files automatically.
- It’s easier to write than Makefile, and convenient to include third-party libraries.
A simple example of CMakeLists.txt, we still suppose we have hello.h
, hello.cpp
and main.cpp
.
cmake_minimum_required(VERSION 3.10)
project(Hello)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_executable(main main.cpp hello.cpp)
To compile and run the project,
cmake -B build -S .
cmake --build build
./build/main
Library
Why use library?
- Sometimes we have multiple executables, they may share some common parts of codes.
- We pack these common parts into a library, and then link them when compiling executables.
- The functions in the library can be used by executables, also other libraries.
Static library
- After compilation, the object code of the library is included in the executable file.
- Easy to use, but the final executable file will become larger.
In CMake, we can create a static library by:
│ CMakeLists.txt
│ main.cpp
│
└───hello
CMakeLists.txt
hello.cpp
hello.h
# CMakeLists.txt under hellolib
add_library(hellolib STATIC hello.cpp)
target_include_directories(hello PUBLIC .)
# CMakeLists.txt under main
cmake_minimum_required(VERSION 3.10)
project(hellocmake)
add_subdirectory(hellolib)
add_executable(main main.cpp)
target_link_libraries(main PUBLIC hellolib)
CMakeLists.txt under hellolib directory
add_library(hellolib STATIC hello.cpp)
tells CMake to build a libraryhellolib
from the source filehello.cpp
.1STATIC
means a static library.
target_include_directories(hellolib PUBLIC .)
:- specifies include directories to use when compiling the target
hellolib
. .
means the current directory.PUBLIC
means when other targets link tohellolib
, they will also use the include directories ofhellolib
.
- specifies include directories to use when compiling the target
Handle header files
target_include_directories(hellolib PUBLIC .)
is crutial in our project.
We want to aovid changing the include directive to
// main.cpp
#include "hello/hello.h"
With the help of this command, no need to change.
// main.cpp
#include "hello.h"
We can even use the following2
// main.cpp
#include <hello.h>
CMakeLists.txt under main directory
# CMakeLists.txt under main
cmake_minimum_required(VERSION 3.10)
project(hellocmake)
add_subdirectory(hellolib)
add_executable(main main.cpp) # no need to specify hello.cpp
target_link_libraries(main PUBLIC hellolib)
add_subdirectory(hellolib)
tells CMake to add the subdirectory into the project.- CMake will execute the CMakeLists.txt in the
hellolib
directory.
- CMake will execute the CMakeLists.txt in the
target_link_libraries(main PUBLIC hellolib)
tells CMake to link the libraryhellolib
to the executablemain
.PUBLIC
means when other targets link tomain
, they will also link tohellolib
.3
Dynamic library
- After compilation, the object code of the library is not included in the executable file.
- The executable file contains “stub” to find and load the library at runtime.
- At runtime
.dll
or.so
files are loaded into memory.- The “stub” in the executable file will find the library in memory and load it.
- Dynamic library is easier to update, everytime you update the library, you don’t need to recompile the executable file. It’s also harder to use and debug.
To create a dynamic library, we can modify the CMakeLists.txt in hellolib
directory to:
add_library(hellolib SHARED hello.cpp)
target_include_directories(hello PUBLIC .)
What we modified is just changing STATIC
to SHARED
.
Assignment
Word Abbreviation: Given a word, output its abbreviation.
Rules:
- If the word has more than 3 characters:
- Keep the first character
- Count the number of characters between the first and last character
- Add this number after the first character
- End with the last character of the original word
- If the word has 3 or fewer characters, leave it unchanged
Examples:
- kubernetes -> k8s
- localization -> l10n
- internationalization -> i18n
- accessibility -> a11y
- documentation -> d11n
- dog -> dog (unchanged)
- do -> do (unchanged)
You can assume that the length of the input word will not exceed 100.
Requirements:
- Download the zip file and configure the project with CMake.
- Implement the function
std::string abbreviate(const std::string& word)
inabbreviate.cpp
. - Test cases are given in
main.cpp
.
CMake Assignment
Given the assignment2 folder, write multiple CMakeLists.txt to compile the project.
- CMakeLists.txt under payoff:
- Create a lib called
payofflib
.
- Create a lib called
- CMakeLists.txt under random:
- Create a lib called
randomlib
.
- Create a lib called
- CMakeLists.txt under root folder of project:
- Compile the project and link the two libraries, the executable file should be named
main
.
- Compile the project and link the two libraries, the executable file should be named
Bonus (5 points)
For the first assignment:
- Implement the function using C-style strings instead of std::string.
- Function declaration:
void abbreviateWord(char* word)
- Requirements:
- Use only control flows and string functions discussed in class (e.g., strlen, strcpy).
- Assume the input char array has a maximum size of 100 characters.
- Modify the string in-place (don’t create a new string).
- Ensure the string is null-terminated after modification, set any unused characters in the array (after the null terminator) to ‘\0’.