作业地址: homework_1.pdf (uni-bonn.de)
练习内容:Bash and build system
下载作业:
$ wget https://www.ipb.uni-bonn.de/html/teaching/modern-cpp-2021/homeworks/homework_1.zip
$ mkdir homework_1
$ unzip homework_1.zip -d ./homework_1/ && rm homework_1.zip
解压之后的作业目录如下所示:
$ tree .
|-- homework_1
| |-- task_1
| | `-- test_folder
| `-- task_2
| |-- include
| | `-- ipb_arithmetic
| |-- results
| | |-- bin
| | `-- lib
| `-- src
|-- homework_2
|-- ...
Assignment A (4 points)
用一行语句回答下面四个问题,将结果保存在homework_1/task_1/commands.sh
中,语句需要在本地的homework_1/task_1
中验证通过。
- 计算
data.dat
有多少行 - 计算包含
dolor
或者dalor
单词的语句的行数 - 计算在
data.dat
中有多少个单词? - 计算这些单词里有多少个以
mol
开头?
提示:wc
命令可能会用到。
答案:
$ cat data.dat | wc -l
$ cat data.dat | grep 'd[a,o]lor' | wc -l
$ cat data.dat | wc -w
$ cat data.dat | tr ' ' '\n' | grep '^mol' | wc -l
Assignment B (6 points)
Build System只是工具而已,我们不用深究,只用知道怎么用即可。在Assignment B 中,会给你一个很简单的主程序,该主程序调用了ipb_arithmetic
中的两个方法,你需要build 主程序、库文件和将两者链接起来。
本部分的文件结构和实际中的项目类似,包括三部分:
- Source Files,用来存放源代码
- Include, 用来存放你的应用/库文件的API
- Results Artifacts,用来存放输出的二进制文件和库文件
Note:如果调用的是没有开源的第三方库,则可能只有Include 和 Results Artifacts。
补充:在Windows中的项目文件夹开发,参考博客
task_2
的文件结构:
.
├── include
│ ├── ipb_arithmetic
│ │ ├── subtract.hpp
│ │ └── sum.hpp
│ └── ipb_arithmetic.hpp
├── LICENSE
├── README.md
├── results
│ ├── bin
│ │ └── example_output
│ └── lib
└── src
├── main.cpp
├── subtract.cpp
└── sum.cpp
Exercise1: Build by hand (2 points)
本部分需要你使用old fashion的方法去build library and main program:
- 使用
-c
选项编译得到目标文件,而不使用linker - 你需要使用
-I ./include/
指定编译器寻找header files的位置 - 你需要通过
-o
选项指定中间输出的文件的名字
提交内容:一份位于repo根目录位置的 完整的 build bash script build.sh
Task 1: Build the ipb_arithmetic library (1 point)
-
创建一个空的文件夹,命名为
./build
-
编译得到
substract.o
和sum.o
,可参考的命令c++ -c -I dir/ src/<filename.cpp> -o build/<name.o>
,此时./build/
目录下会有两个文件:substract.o
和sum.o
-
参考下面的代码创建你的library
# always start the name of your liarary with 'lib' ar rcs build/libipb_arithmetic.a build/sum.o build/substract.o
Task 2: Build the example program (1 point)
现在你需要build你的主程序,然后和刚刚的库文件链接在一起,当你一切结束后,需要把二进制文件放在results/bin/
下面,并命名为test_ipb_arithmetic
Answer for Task1
#!/bin/bash
mkdir build
c++ -c ./src/subtract.cpp -I include/ -o build/subtract.o
c++ -c ./src/sum.cpp -I include/ -o build/sum.o
c++ -c ./src/main.cpp -I include/ -o build/main.o
ar rcs build/libipb_arithmetic.a build/sum.o build/subtract.o
c++ build/main.o -o build/test_ipb_arithmetic -L build/ -l ipb_arithmetic
cp build/test_ipb_arithmetic results/bin/test_ipb_arithmetic
注意:c++的链接命令,-L
后面是库文件的目录地址,-l
后面是库文件的名称(不要加lib
前缀和.a
后缀)。
执行后的输出结果:
Exercise2 : Build by cmake (2points)
该部分使用CMake编译项目,特别注意CMake的版本变更很快,在网上查找资料时,一定注意自己所阅读的doc的版本,在ubuntu20.04中,CMake的版本是3.21.3。(注,笔者所用的cmake版本是3.16.3,可以使用cmake --version
查看)
这次我们依然基于Exercise1中的例子,不过是采用cmake实现,需要提交CMakeLists.txt
文件。
Answer for Task2
在Task2中可能要提供多个CMakeLists.txt(注意这里可以参考cmake官方的tutorial)
cmake_minimum_required(VERSION 3.1)
project(MyMath)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_subdirectory(src)
add_executable(test_ipb_arithmetic src/main.cpp)
target_link_libraries(test_ipb_arithmetic PUBLIC ipb_arithmetic)
target_include_directories(test_ipb_arithmetic PUBLIC "${PROJECT_SOURCE_DIR}/include" )
src/CMakeLists.txt
:
add_library(ipb_arithmetic subtract.cpp sum.cpp)
# 尤其注意这里的头文件是给subtract.cpp 和 sum.cpp 的,千万不要漏掉
# 如果库的开发者并不需要引用项目的内容,则可以将此处修改为INTERFACE,这是现代cmake的体现,即 Usage Requirement
target_include_directories(ipb_arithmetic
PRIVATE ${CMAKE_SOURCE_DIR}/include
)
此时的文件夹结构如下所示:
我们运行测试结果是否正确:
结果一切OK
Exercise3 : Install your library (2points)
这里提供了一些你将来使用第三方library的建议:
- 在当前repo下创建一个
./install/
文件夹 - 提供一个
install.sh
脚本用来安装在发送你的库文件时所需要的所有文件,值得一提的是,这些文件应当在results
文件夹中。 - 更新你的
CMakeLists.txt
,以便增加新的target,例如当你运行make install
来build 系统的时候,它将会在install
文件夹中安装所有需要的文件。
提示1:你应该使用变量CMAKE_INSTALL_PREFIX
来指向本地的./local
目录,否则cmake将在你的电脑上安装第三方库,这需要超级管理者权限。
提示2:install
函数可能会有帮助
提示3:当完成所有的练习后,这应当是你所提交的文件夹的样子(当然在cmake
之前)
|-- build
| |-- libipb_arithmetic.a
| |-- main.o
| |-- subtract.o
| |-- sum.o
| `-- test_ipb_arithmetic
|-- build.sh
|-- CMakeLists.txt
|-- include
| |-- ipb_arithmetic
| | |-- subtract.hpp
| | `-- sum.hpp
| `-- ipb_arithmetic.hpp
|-- install
| |-- include
| | |-- ipb_arithmetic
| | | |-- subtract.hpp
| | | `-- sum.hpp
| | `-- ipb_arithmetic.hpp
| `-- lib
| `-- libipb_arithmetic.a
|-- LICENSE
|-- README.md
|-- results
| |-- bin
| | |-- example_output
| | `-- test_ipb_arithmetic
| `-- lib
| `-- libipb_arithmetic.a
`-- src
|-- CMakeLists.txt
|-- main.cpp
|-- subtract.cpp
`-- sum.cpp
Answer for Task 3
首先,我们在当前repo的目录下创建bash 脚本文件 install.sh
,把results/
文件夹填充成最终想要的效果:
# build.sh执行之前的results/
|-- results
| |-- bin
| `-- example_output
| `-- lib
# build.sh执行之后的results/
|-- results
| |-- bin
| | |-- example_output
| | `-- test_ipb_arithmetic
| `-- lib
| `-- libipb_arithmetic.a
注意这里的test_ipb_arithmethic
就是可执行文件,在Excercise1中我们把它放在build/
下面,然后通过下面的install.sh
将文件拷贝到results/bin
中:
# install.sh
cp build/libipb_arithmetic.a results/lib/
cp build/test_ipb_arithmetic results/bin/
这里比较让我疑惑的是,最终要提交的结果中并没有install.sh
,我觉得可以把这里的两条命令添加到build.sh
中(其实第二条命令在build.sh
中已经有了),修改后的build.sh
如下:
#!/bin/bash
mkdir build
c++ -c ./src/subtract.cpp -I include/ -o build/subtract.o
c++ -c ./src/sum.cpp -I include/ -o build/sum.o
c++ -c ./src/main.cpp -I include/ -o build/main.o
ar rcs build/libipb_arithmetic.a build/sum.o build/subtract.o
c++ build/main.o -o build/test_ipb_arithmetic -L build/ -l ipb_arithmetic
cp build/test_ipb_arithmetic results/bin/test_ipb_arithmetic
cp build/libipb_arithmetic.a results/lib/
第二步,我们修改顶层的CMakeLists.txt
最终要实现的效果如下所示:
|-- install
| |-- include
| | |-- ipb_arithmetic
| | | |-- subtract.hpp
| | | `-- sum.hpp
| | `-- ipb_arithmetic.hpp
| `-- lib
| `-- libipb_arithmetic.a
关于cmake中的install命令,可以参考CMake之install方法的使用 - 知乎 (zhihu.com),这里最终要求的install/
文件夹位于本项目内,而不是默认的usr/local/
,所以作业中建议修改根目录选项CMAKE_INSTALL_PREFIX
:
# 修改安装路径为当前项目目录下的install/文件夹
set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/install)
按照作业的要求,我们使用install
将库文件、可执行文件和头文件安装到指定目录,这里的根目录就是上面的CMAKE_INSTALL_PREFIX
:
# 修改安装路径为当前项目目录下的install/文件夹
set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}/install)
# 将库文件、可执行文件和头文件安装到指定目录
install(TARGETS test_ipb_arithmetic ipb_arithmetic
EXPORT MyLibTargets # find_package会用到
# LIBRARY DESTINATION lib # 动态库安装路径
ARCHIVE DESTINATION lib # 静态库安装路径
RUNTIME DESTINATION bin # 可执行文件安装路径
)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/include DESTINATION include )
从最终结果看,这里并没有要求安装可执行文件以及版本信息等额外的cmake文件,所以略去。
这里修改后的CMakeLists.txt
如下:
cmake_minimum_required(VERSION 3.1)
project(MyMath)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# 修改安装路径为当前项目目录下的install/文件夹
set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}/install)
add_subdirectory(src)
add_executable(test_ipb_arithmetic src/main.cpp)
target_link_libraries(test_ipb_arithmetic PUBLIC ipb_arithmetic)
target_include_directories(test_ipb_arithmetic PUBLIC "${PROJECT_SOURCE_DIR}/include" )
# 将库文件、可执行文件和头文件安装到指定目录
install(TARGETS test_ipb_arithmetic ipb_arithmetic
EXPORT MyLibTargets # find_package会用到
# LIBRARY DESTINATION lib # 动态库安装路径
ARCHIVE DESTINATION lib # 静态库安装路径
RUNTIME DESTINATION bin # 可执行文件安装路径
)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/include DESTINATION include )
src/CMakeLists.txt
不需要修改:
add_library(ipb_arithmetic subtract.cpp sum.cpp)
# 尤其注意这里的头文件是给subtract.cpp 和 sum.cpp 的,千万不要漏掉
# 如果库的开发者并不需要引用项目的内容,则可以将此处修改为INTERFACE,这是现代cmake的体现,即 Usage Requirement
target_include_directories(ipb_arithmetic
PRIVATE ${CMAKE_SOURCE_DIR}/include
)
cmake过程的输出:
最后cmake之后的install/
最终整理后的项目结构:
参考文献:
[1] 有关Git基本使用方法:ubuntu配置git并使用github管理项目
[2] 有关CMake的更多使用方法,可以参考了CMake Tutorial — CMake 3.17.5 Documentation、CMake教程_知乎简略版、CMake教程_CSDN完整版,后两个教程基本就是官方Tutorial的翻译,所以更推荐官方教程。