Deb 打包——从入门到入魂(一)

Debian 软件包的简单介绍

“Debian“软件包”,或称作 Debian 档案文件,包含了与特定的程序套件或一组关联的程序有关的可执行文件、库和文档。正常情况下,Debian 档案文件的文件名以 .deb 结尾。” ——《第 7 章 Debian 软件包管理系统基础》

简单来讲,Debian 软件包就是一个软件的压缩包,只不过里面附带了很多其他的信息(如版本、依赖等等)来让系统能方便地处理他。

Debian 软件包的类型

Debian 软件包有两种类型:

  • 二进制包,包含了可执行文件、配置文件、man/info 页面、版权信息,以及其他文档。这些软件包使用一种 Debian 特有的存储格式进行分发(参见第 7.2 节 “Debian 二进制软件包的格式是什么?”);它们的扩展名通常是“.deb”。二进制包可以使用 Debian 的 dpkg 工具进行解包(也许会通过 aptitude 等前端进行调用);更多细节请阅读手册页。

  • 源码包,包含了一个 .dsc 文件描述该源码包(包括下述文件的文件名),一个 .orig.tar.gz 文件,使用经过 gzip 压缩的 tar 档案保存未经修改的源代码,通常还包括一个 .diff.gz 文件,保存了 Debian 对源代码的修改。dpkg-source 工具可以打包和解包 Debian 源码包。

通常我们安装的都是二进制包。在Debian系统中,二进制包通常都是用源码包构建出来的。一个源码包可以构建出多个二进制包。

Debian 软件包的结构

Deb 包是一个使用 ar 压缩的压缩包。里面主要包含了 control.tar.gzdata.tarcontrol.tar.gz 包含了软件包的元数据,如软件包的名称、版本、依赖等等。这些信息使得软件包管理器能够识别并正确安装软件。data.tar 就是软件包的内容啦!包管理器会把里面的文件解压到系统的相应位置。

Debian 解压后的文件结构

当你手动解压(没错!就是好玩)一个deb包后,目录结构是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
.
├── DEBIAN
│   ├── control
│   └── postinst
├── usr
│   ├── bin
│   │   └── hello
│   └── share
│   └── man
│   └── man1
│   └── hello.1.gz
└── etc
└── hello.conf

大家很快就发现,这不就是把软件解压了嘛!只不过,有个特殊的文件夹——DEBIAN。这个文件夹内存放的是一些特殊的文件,如controlpostinst等等。这些文件会在软件包安装时被用到。下面会对这些文件进行介绍。

control 文件

简短地说,Debian 软件包 hello 的控制文件样例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
Package: hello
Version: 2.9-2+deb8u1
Architecture: amd64
Maintainer: Santiago Vila <[email protected]>
Installed-Size: 145
Depends: libc6 (>= 2.14)
Conflicts: hello-traditional
Breaks: hello-debhelper (<< 2.9)
Replaces: hello-debhelper (<< 2.9), hello-traditional
Section: devel
Priority: optional
Homepage: https://www.gnu.org/software/hello/
Description: example package based on GNU hello

Package 字段给出了软件包的名称。这个名字可以传递给软件包管理工具,以操作该软件包,且它通常与 Debian 档案文件的文件名的第一部分相似,但不一定完全相同。

Version 字段给出了上游开发者的版本号以及(在最后一部分)Debian 软件包的修订级别。

Architecture 字段指明了该二进制软件包编译时针对的处理器。

Depends 字段给出了安装该软件包前必须安装的软件包列表。

Installed-Size指出了该软件包安装后会消耗多少磁盘空间。设置该字段的目的是给软件包安装前端使用,以判断是否有足够的磁盘空间安装该程序。

Section 行给出了该 Debian 软件包在 Debian 档案站点所存储的“分区”。

Priority 指出了该软件包在安装时有多重要,这使得半智能的软件,如 apt 或 aptitude,能够将软件包分为各种类别,例如,可选择安装的软件包。参见第 7.7 节 “什么是必备、必需、重要、标准、可选和额外软件包?”。

Maintainer 字段给出了当前负责维护该软件包的人的电子邮件地址。

Description 字段给出了该软件包功能的简短介绍。

preinstpostinstprerm,和 postrm 脚本

这些文件是在软件包安装或卸载前后自动执行的可执行脚本。这些文件和名为 control 的文件都属于 Debian 档案文件的“控制”部分。

preinst

  • 该脚本在所属的软件包从它的 Debian 档案文件(“.deb”)中被解压之前执行。许多“preinst”脚本会停止和正在升级的软件包有关的服务,直至安装或升级完成(即在“postinst”脚本成功执行之后)。

postinst

  • 该脚本在 foo 从它的 Debian 档案文件(“.deb”)中被解压之后执行,通常用于完成软件包 foo 所必需的配置。“postinst”脚本常常请求用户输入,且/或在用户接受默认值的时候警告他们,应记得在需要时回来重新配置软件包。许多“postinst”脚本会在新软件包的安装或升级完成之后执行必要的命令,以启动或重新启动服务。

prerm

  • 该脚本通常停止与软件包有关联的服务。它在删除与软件包相关的文件之前执行。

postrm

  • 该脚本通常修改与 foo 相关的链接或其他文件,且/或删除该软件包创建的文件。

看了这么多,相信大家也了解了基本的deb包格式:一个基本的deb包只需要一个control文件和需要被打包的目录就好!下面就来为大家介绍这第一种原始的打包方式。

方法一:手动打包

Step1:建立目录结构

我们的程序是要放到一个系统中的各个部分去的。

其实这个并没有那么严格,不一定全都要挤到/usr/lib里边去,放到/home等都没有太大的问题——看你需求。

比如如下的结构:

1
2
3
4
5
6
7
8
9
10
11
├── debpack
│ ├── DEBIAN(这个目录要添加control文件(无后缀名),可选添加postinst等)
│ └── usr
│ ├── lib
│ │ └── debpacktest(预定要安装到系统中的文件)
│ │ ├── mainform.py
│ │ └── main.py
│ └── share
│ ├── applications(在这里添加xxx.desktop,使启动器中能够加载出你的应用,没有就不需要)
│ └── icons(你的图标,没有就不需要)

上述结构会将 main.py 安装到 /usr/lib/debpacktest 目录下。

大家可能也发现了:其实就是把目录底下的文件结构原封不动地搬到系统根目录下了!

Step2:添加control文件

进入到到DEBIAN,在命令行输入(当然,你用gedit也行)

1
$ nano control

输入以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
Package: hello # 软件包的报名
Version: 2.9-2+deb8u1 # 软件包的版本
Architecture: amd64 # 软件包运行的架构
Maintainer: Santiago Vila <[email protected]> # 维护者姓名邮箱
Installed-Size: 145 # 安装后的大小
Depends: libc6 (>= 2.14) # 软件包的依赖,使用英文逗号分隔,没有无需此行
Conflicts: hello-traditional # 和哪些软件包冲突,使用英文逗号分隔,没有无需此行
Breaks: hello-debhelper (<< 2.9) # 列出这个包会破坏哪些包,包管理会尝试修复被破坏的包。
Replaces: hello-debhelper (<< 2.9), hello-traditional # 指出此包替代了哪些包
Section: devel # 软件包的分区
Priority: optional # 软件包的优先级,一般都用 optional
Homepage: https://www.gnu.org/software/hello/ # 软件包的官网
Description: example package based on GNU hello # 软件包的简介

记得不要把注释写进去了~!按照实际情况进行修改。

Step3:打包

到这里,一个基本的deb软件包就大功告成了。

我们返回到 debpack 的上级目录。执行如下命令:

1
fakeroot dpkg -b debpack hello_2.9-2+deb8u1_amd64.deb

fakeroot 用于模拟root的文件所有权,保证安装文件的权限。如果提示没有,可以用 sudo apt install fakeroot 进行安装。

Step4:查看打包结果

使用 dpkg -c hello_2.9-2+deb8u1_amd64.deb 可以查看deb包中的内容。

可以看到,这个方法十分复杂。通常我们的程序比较庞大,会拥有许多依赖。此时再像上面这样手动打包就显得十分不便。在后续的文章中将为大家介绍更方便的打包方式。

CMake简单入门

简介

CMake是一个十分好用的构建系统,它可以帮助大家构建许多的C/C++应用程序。然而,CMake的配置方式十分复杂,网上的资料又令人眼花缭乱。这些困难经常让同学们望而却步。所以,本文将帮助大家轻松了解CMake的使用~

通过这个速通版本,大家会学会如何快速的使用CMake来编译C/C++工程。想要详细学习的同学,可以继续阅读本章剩余小节。

基础知识回顾

在开始学习CMake之前,大家需要知道以下名词的含义

  1. 绝对路径
  2. 相对路径
  3. 当前目录
  4. 父目录
  5. 子目录
    如果你很确定这些名称的含义,可以跳过这一小节的回顾啦。

绝对路径和相对路径

  1. 绝对路径是从硬盘或者根目录开始。
    1. Windows当中,绝对路径的起始为硬盘盘符,例子如下:
      1. C:/Windows/User/Desktop
    2. LinuxUnix系统中,绝对路径的起始为根目录/,例子如下:
      1. /home/username/Desktop

当前目录、父目录、子目录

  1. 当前目录指的是终端或文件所处的位置。
    1. 对于终端,使用pwd命令可以直接显示出当前目录的绝对路径。
    2. 对于文件或文件夹,其所处的目录称为该文件的当前目录。
  2. 父目录是当前目录的上一级目录。
  3. 子目录是当前目录的下一级目录。

    CMake基础

最基础的CMakeLists.txt模板

下面是一个最简单的CMakeLists.txt模板,使用它可以编译一个最简单的C++程序。

1
2
3
4
5
6
7
cmake_minimum_required(VERSION 3.5)

# Set the project name
project(hello_cmake)

# add executable files to the project
add_executable(${PROJECT_NAME} main.cpp main.h)

接下来,我们会一起来了解这些内容的含义,以及如何增加内容来满足我们的需求~

注释

与其他的编程语言一样,在CMake中同样可以使用注释。CMake中的注释符号是#。这个符号适用于一行文字的注释,下面是一个简单例子:

1
2
#This is a useless comment
#同样可以写中文

变量

CMake同样支持定义和使用变量!不过语法比较奇怪,需要大家慢慢来熟悉~ (´▽`)

变量的定义

在CMake当中,使用如下的语法来定义一个变量:

1
set(Name Value)

其中,Name是变量的名称,Value是变量的值。变量的值可以是字符串、数字或者是布尔值(True/False)等等。

例如,

1
2
3
4
5
6
7
8
9
10
# 定义一个布尔变量
set(MyBool True)

# 定义一个字符串 "SomeString"
set(MyString SomeString)
# 也可以带上引号
set(MyString2 "SomeString")

# 定义一些数字
set(MyNumber 123)

变量的访问

在CMake当中,使用下面的格式访问变量:

1
${Name}

其中,Name是变量名。

一些使用的例子如下:

1
message(${MyString}) # 打印变量的值

我们注意到,${Name}这个语句整体,其实就代表了这个变量的值了

添加源文件(.c/.cpp)

基本方法

在上面的最简单的例子当中,我们可以看到,

1
add_executable(${PROJECT_NAME} main.cpp)

这个语句是最简单的写法,它将当前目录(根目录)下的main.cpp文件添加到了编译目标里面。同时指定了生成的可执行文件(executable)名称为变量${PROJECT_NAME}的值。在上面的例子中,这个值就是hello_cmake

高级方法

如果只有几个源文件,那么非常好解决!直接像上面的main.cpp一样手动添加进来就好~

可是,如果我们有许许多多的文件需要添加,手动修改的方式未免效率低下,同时也难以维护。因此,我们使用一种新的方法——通配符匹配!

这种方法的语法如下:

1
file(GLOB_RECURSE SOURCES *.cpp *.c)

上面这句话的意思是,在根目录下递归查找后缀是.cpp.c的文件,并将结果保存在名为SOURCES的变量中。注意,这个变量的值是list类型的~

于是乎,我们用这个方法将项目所有目录下的源文件都添加进去:

1
2
file(GLOB_RECURSE SOURCES *.cpp *.c)
add_executable(${PROJECT_NAME} ${SOURCES})

这样就完成啦!

添加头文件(*.h/*.hpp)

基本方法

与添加源文件是一样的,头文件一样可以使用add_executable命令:

1
add_executable(${PROJECT_NAME} main.cpp main.h)

这样我们就将根目录里的main.h添加进去了。

需注意,include的时候还是按照路径引用哦!如果需要添加包含目录,用下面的 include_directories就好啦

高级方法

方法与上面添加源文件一样

1
2
file(GLOB_RECURSE HEADERS *.h *.hpp)
add_executable(${PROJECT_NAME} main.cpp ${HEADERS})

就不再过多解释了。

添加一个目录

大多数情况下,头文件都是放在了一个目录下面的,在这种情况下,我们只用包含这个目录就好:

1
include_directories(DirName)

其中,DirName是目录相对于根目录的地址。

比如,在项目的根目录下面有一个名为Inc的文件夹,其中有一些头文件:

1
include_directories(Inc)

如果有子目录的话,用法是一样的:

1
2
# 包含了 ./include/myapp 的文件夹
include_directories(include/myapp)

完整版基本配置

现在,大家已经学会了基本的CMake操作了,下面将会给出一个带注释的完整版本:

1
2
3
4
5
6
7
8
9
10
11
12
# 设置最低兼容的CMake版本
cmake_minimum_required(VERSION 3.18.2)
# 设置工程名称,储存在PROJECT_NAME变量中
project(hello_cmake)

# 包含了 ./include/myapp 的文件夹
include_directories(include/myapp)

# 查找根目录下的所有源文件
file(GLOB_RECURSE SOURCES *.cpp *.c)
# 添加可执行文件
add_executable(${PROJECT_NAME} ${SOURCES})

CMake 简单入门

前言

由于目前许多的C/C++项目都是用的CMake进行构建(e.g. Qt、ROS、STM32),了解并掌握CMake的使用方法已经变得越来越重要啦!

本文可以为大家提供一个CMake的快速入门教程~ 同时,也可作为大家忘记如何使用是的一个reminder!

环境配置

咱的推荐配置如下~

1
2
3
4
5
An operating system: Linux/Windows/macOS/Unix
Development toolkit: GCC/Clang/MSVC
CMake: Latest Version
make: legacy build system
ninja: Recommended to replace the make command. Not necessarily needed.

上述环境请大家根据所使用的系统进行安装!

一个配置好的环境参考如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PS D:\> ninja --version
1.11.1
PS D:\> gcc --version
gcc.exe (tdm64-1) 10.3.0
Copyright (c) 2020 Free Software Foundation, Inc.
本程序是自由软件;请参看源代码的版权声明。本软件没有任何担保;
包括没有适销性和某一专用目的下的适用性担保。
PS D:\> cmake --version
cmake version 3.25.2

CMake suite maintained and supported by Kitware (kitware.com/cmake).
PS D:\> cl
用于 x64 的 Microsoft (R) C/C++ 优化编译器 19.29.30147 版
版权所有(C) Microsoft Corporation。保留所有权利。

用法: cl [ 选项... ] 文件名... [ /link 链接选项... ]

Step 1:构建最小项目

接下来的教程将以命令行为主~

最基本的项目是将一个源代码文件生成可执行文件。对于这么简单的项目,只需要一个三行的 CMakeLists.txt 文件即可,这是本篇教程的起点。在目录中创建一个 CMakeLists.txt 文件,如下所示:

1
2
3
4
5
6
7
8
9
10
# Set the minimum version of CMake that can be used
# To find the cmake version run
# $ cmake --version
cmake_minimum_required(VERSION 3.5)

# Set the project name
project(hello_cmake)#

# add executable files to the project
add_executable(${PROJECT_NAME} main.cpp)

在上述文件中,cmake_minimum_required 指定使用 CMake 的最低版本号,project 指定项目名称,add_executable 用来生成可执行文件,需要指定生成可执行文件的名称和相关源文件。PROJECT_NAME 是CMake内置的变量,其内容就是咱之前设定的项目名称~

其中,main.cpp 文件位于和 CMakeLists.txt 同一目录下,内容如下~

1
2
3
4
5
6
# include <iostream>

int main(int argc, char **argv[]) {
std::cout << "Hello World!" << std::endl;
return 0;
}

这里咱实现了简单的Hello World程序~

构建、编译和运行

现在,咱们就可以构建、编译和运行这个小项目啦!就是先运行 cmake 命令来构建项目,然后使用你选择的编译工具进行编译。

使用如下指令进行构建:

1
2
3
4
mkdir build 
cd build
cmake -G Ninja ..
ninja

输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
PS D:\Projects\CMake-tutorials\build> cmake -G Ninja ..
-- The C compiler identification is GNU 10.3.0
-- The CXX compiler identification is GNU 10.3.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: D:/opt/TDM-GCC-64/bin/gcc.exe - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: D:/opt/TDM-GCC-64/bin/c++.exe - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: D:/Projects/CMake-tutorials/build
PS D:\Projects\CMake-tutorials\build> ninja
[1/2] Building CXX object CMakeFiles/hello_cmake.dir/main.cpp.obj
D:/Projects/CMake-tutorials/main.cpp:3:5: warning: second argument of 'int main(int, char***)' should be 'char **' [-Wmain]
3 | int main(int argc, char **argv[]) {
| ^~~~
[2/2] Linking CXX executable hello_cmake.exe

此处,咱们使用了 ninja 进行构建,如果你没有安装,建议大家安装一个~

如果使用的是 make 进行构建,则运行下面的命令:

Unix/macOS/Linux

1
2
3
4
mkdir build 
cd build
cmake -G "Unix Makefiles" ..
make

Windows

1
2
3
4
mkdir build 
cd build
cmake -G "MinGW Makefiles" ..
mingw32-make

或者你使用Visual Studio来编译~

1
2
3
4
mkdir build
cd build
cmake -G "Visual Studio 16 2019" ..
msbuild -consoleloggerparameters:Verbosity=minimal ALL_BUILD.vcxproj

输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
PS D:\Projects\CMake-tutorials\build> cmake -G "Visual Studio 16 2019" ..
-- Selecting Windows SDK version 10.0.19041.0 to target Windows 10.0.22621.
-- The C compiler identification is MSVC 19.29.30147.0
-- The CXX compiler identification is MSVC 19.29.30147.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: D:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.29.30133/bin/Hostx64/x64/cl.exe - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: D:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.29.30133/bin/Hostx64/x64/cl.exe - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: D:/Projects/CMake-tutorials/build
PS D:\Projects\CMake-tutorials\build> msbuild -consoleloggerparameters:Verbosity=minimal ALL_BUILD.vcxproj
用于 .NET Framework 的 Microsoft (R) 生成引擎版本 16.11.2+f32259642
版权所有(C) Microsoft Corporation。保留所有权利。

Checking Build System
Building Custom Rule D:/Projects/CMake-tutorials/CMakeLists.txt
main.cpp
hello_cmake.vcxproj -> D:\Projects\CMake-tutorials\build\Debug\hello_cmake.exe
Building Custom Rule D:/Projects/CMake-tutorials/CMakeLists.txt

现在,让我们来了解一下发生了什么~~

1
cmake -G Ninja ..

此时在 build 目录下会生成 build.ninja 文件,然后调用编译器来实际编译和链接项目:

1
ninja

或者执行

1
cmake --build .

--build 指定编译生成的文件存放目录,其中就包括可执行文件, . 表示存放到当前目录,

build 目录下生成了一个 hello_cmake.exe 可执行文件,试着执行它:

1
2
PS D:\Projects\CMake-tutorials\build> hello_cmake.exe
Hello World!

现在,咱的第一个CMake程序就完成啦~

此时,我们的工作目录应该类似:

1
2
3
4
5
D:.
│ CMakeLists.txt
│ main.cpp

└─build

外部构建与内部构建

这里创建了一个 build 目录存放编译产物,可以避免编译产物与代码文件混在一起,这种叫做外部构建。

还有一种内部构建,即直接在项目根目录下进行构建系统与编译,这时构建和编译命令就更改为:

1
2
cmake -G Ninja .
cmake --build .

内部构建会使得项目文件很混乱,一般直接用外部构建即可。

Step 2:优化 CMakeLists.txt 文件

现在,我们已经拥有了一个最小的CMake项目啦~

可是,细心的你或许已经发现:

  • 咱有多个源文件怎么办啊?
  • 咱用的外部库怎么添加呢?
  • 咱想手动指定编译器

要想解决上述问题,我们需要优化 CMakeLists.txt 文件!

set 与 PROJECT_NAME

set 指令用于在CMake文件中指定变量,设置好变量后,可以在之后用 ${变量名} 多次使用它!

比如之前我们设置的 PROJECT_NAME 就是一个变量~

e.g. 我们可以用一个变量表示多个源文件:

1
2
set(SRC_LIST a.cpp b.cpp c.cpp)
add_executable(${PROJECT_NAME} ${SRC_LIST})

于是原来的 CMakeLists.txt 文件就可以变成如下所示:

1
2
3
4
5
6
7
8
9
cmake_minimum_required(VERSION 3.5)

# set the project name
project(hello_cmake)

SET(SRC_LIST main.cpp)

# add the executable
add_executable(${PROJECT_NAME} ${SRC_LIST})

这样看起来就很简洁。但是一个个输入文件的位置还是比较繁琐,之后咱会介绍一个更简洁的方案~

添加版本号和配置头文件

有时候我们需要在工程中调用我们定义的版本号,但是一个一个的修改未免显得过于麻烦。我们可以在 CMakeLists.txt 为可执行文件和项目提供一个版本号。

首先,我们来修改 CMakeLists.txt 文件,使用 project 命令设置项目名称和版本号。

1
2
3
4
cmake_minimum_required(VERSION 3.5)

# Set the project name
project(hello_cmake VERSION 1.0)

然后,配置头文件来将版本号传递给源代码:

1
configure_file(TutorialConfig.h.in TutorialConfig.h)

由于 TutorialConfig.h 文件会被自动写入 build 目录,因此必须将该目录添加到搜索头文件的路径列表中。将以下行添加到 CMakeLists.txt 文件的末尾:

1
2
3
target_include_directories(${PROJECT_NAME} PUBLIC
${PROJECT_BINARY_DIR}
)

PROJECT_BINARY_DIR 表示当前工程的二进制路径,即编译产物会存放到该路径,此时PROJECT_BINARY_DIR 就是 build 所在路径。

然后创建 TutorialConfig.h.in 文件,包含以下内容:

1
2
3
// the configured options and settings for Tutorial
#define Tutorial_VERSION_MAJOR @hello_cmake_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @hello_cmake_VERSION_MINOR@

其中,hello_cmake 是大家配置的项目名称,至于常量名称可以自行修改~

当使用 CMake 构建项目后,会在 build 中生成一个 TutorialConfig.h 文件,内容如下:

1
2
3
// the configured options and settings for Tutorial
#define Tutorial_VERSION_MAJOR 1
#define Tutorial_VERSION_MINOR 0

下一步在 main.cpp 包含头文件 TutorialConfig.h,最后通过以下代码打印出可执行文件的名称和版本号。

1
2
3
4
5
6
7
if (argc < 2) {
// report version
std::cout << argv[0] << " Version " << Tutorial_VERSION_MAJOR << "."
<< Tutorial_VERSION_MINOR << std::endl;
std::cout << "Usage: " << argv[0] << " number" << std::endl;
return 1;
}

输出类似如下:

1
2
3
4
PS D:\Projects\CMake-tutorials\build> ."D:/Projects/CMake-tutorials/build/hello_cmake.exe"
Hello World!
0x218c3217ba0 Version 1.0
Usage: 0x218c3217ba0 number

指定 C++ 标准

不同版本的编译器,默认的C++标准是不同哒~ 所以我们需要手动设定版本来保证兼容性!

比如咱想使用C++11 的 auto 关键字和 for 循环。

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
#include <vector>
int main(void)
{
std::vector<int> arr = { 1, 2, 3 };
// ...
for(auto n : arr) //使用基于范围的for循环
{
std::cout << n << std::endl;
}
return 0;
}

在 CMake 中支持特定 C++标准的最简单方法是使用 CMAKE_CXX_STANDARD 标准变量。在 CMakeLists.txt 中设置 CMAKE_CXX_STANDARD 为11,CMAKE_CXX_STANDARD_REQUIRED 设置为True。确保在 add_executable 命令之前添加 CMAKE_CXX_STANDARD_REQUIRED 命令。

1
2
3
4
5
6
7
cmake_minimum_required(VERSION 3.5)

# Set the project name
project(hello_cmake VERSION 1.0)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

需要注意的是,如果你的gcc编译器版本够高,也可以不用指定 C++ 版本为 11。从 GCC 6.1 开始,当不指定任何版本 C++ 标准时,默认版本是 C++ 14,如果你想用 C++17 的语言,还是需要指定的。

修改完成后,需要对代码进行重新编译 cmake –build .,此时可以不用进行项目构建。

Step 3:添加库

现在我们将向项目中添加一个库,这个库包含计算数字平方根的实现,可执行文件使用这个库,而不是编译器提供的标准平方根函数。

我们把库放在名为 MathFunctions 的子目录中。此目录包含头文件 MathFunctions.h 和源文件 mysqrt.cpp。源文件有一个名为 mysqrt 的函数,它提供了与编译器的 sqrt 函数类似的功能,MathFunctions.h 则是该函数的声明。

创建 MathFunctions.h:

1
double mysqrt(double);

创建 mysqrt.cpp:

1
2
3
4
5
6
7
8
9
10
#include "MathFunctions.h"

double mysqrt(double num) {
double i = 0;
double small;
while (i * i < num)
i += 0.01;
small = i - 1;
return (i*i - num > num - small * small ? small : i);
}

在 MathFunctions 目录下创建一个 CMakeLists.txt 文件,并添加以下一行:

1
2
# MathFunctions/CMakeLists.txt
add_library(MathFunctions mysqrt.cpp)

表示添加一个叫 MathFunctions 的库文件。

修改一下之前的 main.cpp 来使用我们新建的库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# include "TutorialConfig.h"
# include <cmath>
# include <iostream>
# include <string>
# include "MathFunctions.h"

int main(int argc, char* argv[]) {
if (argc < 2) {
std::cout << "Usage: " << argv[0] << " number" << std::endl;
return 1;
}

// convert input to double
const double inputValue = std::stod(argv[1]);

// calculate square root
const double outputValue = mysqrt(inputValue);
std::cout << "The square root of " << inputValue
<< " is " << outputValue
<< std::endl;
return 0;
}

修改 根目录下载的 CMakeLists.txt ,添加如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# add the MathFunctions library
add_subdirectory(MathFunctions)

# add the executable
add_executable(${PROJECT_NAME} tutorial.cpp)

target_link_libraries(${PROJECT_NAME} PUBLIC MathFunctions)

# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
target_include_directories(${PROJECT_NAME} PUBLIC
${PROJECT_BINARY_DIR}
${PROJECT_SOURCE_DIR}/MathFunctions
)

此时,项目应该可以正常编译使用啦~

1
2
PS D:\Projects\CMake-tutorials\build> ."D:/Projects/CMake-tutorials/build/hello_cmake.exe" 123
The square root of 123 is 11.1

Step 4:将库设置为可选项

现在将 MathFunctions 库设为可选的,虽然对于本教程来说,没有必要这样做,但对于较大的项目来说,这种情况很常见。因为不是所有的计算机都会安装项目推荐的所有依赖项~

第一步是向顶级 CMakeLists.txt 文件添加一个选项。

1
option(USE_MYMATH "Use tutorial provided math implementation" ON)

option 表示提供用户可以选择的选项。命令格式为:option(<variable> "description [initial value])

USE_MYMATH 的默认值 ON,用户可以更改这个值。此设置将存储在缓存中,所以不需要在每次构建项目时设置该值。

修改咱的 CMakeLists.txt 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# Set the minimum version of CMake that can be used
# To find the cmake version run
# $ cmake --version
cmake_minimum_required(VERSION 3.5)

# Set the project name
project(hello_cmake VERSION 1.0)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

option(USE_MYMATH "Use tutorial provided math implementation" ON)

if(USE_MYMATH)
add_subdirectory(MathFunctions)
list(APPEND EXTRA_LIBS MathFunctions)
list(APPEND EXTRA_INCLUDES ${PROJECT_SOURCE_DIR}/MathFunctions)
endif()

configure_file(TutorialConfig.h.in TutorialConfig.h)

# add executable files to the project
add_executable(${PROJECT_NAME} main.cpp)
target_include_directories(${PROJECT_NAME} PUBLIC
${PROJECT_BINARY_DIR}
${EXTRA_INCLUDES}
)

target_link_libraries(${PROJECT_NAME} PUBLIC ${EXTRA_LIBS})

if 块中,有 add_subdirectory 命令和 list 命令,APPEND 表示将元素 MathFunctions 追加到列表EXTRA_LIBS 中,将元素 ${PROJECT_SOURCE_DIR}/MathFunctions 追加到列表 EXTRA_INCLUDES 中。EXTRA_LIBS 存储动态/静态链接库,EXTRA_INCLUDES 存储头文件。

接下来对源代码的进行修改。首先,在 main.cpp 中包含 MathFunctions.h 头文件:

1
2
3
#ifdef USE_MYMATH
#include "MathFunctions.h"
#endif

然后,还在 main.cpp 中,使用 USE_MYMATH 选择使用哪个平方根函数:

1
2
3
4
5
#ifdef USE_MYMATH
const double outputValue = mysqrt(inputValue);
#else
const double outputValue = sqrt(inputValue);
#endif

因为源代码使用了 USE_MYMATH 宏,可以用下面的行添加到 TutorialConfig.h.in 文档中:

1
2
// TutorialConfig.h.in
#cmakedefine USE_MYMATH

我们再使用 cmake 命令构建项目,并运行生成的可执行文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
PS > cmake -G Ninja ..
-- The C compiler identification is GNU 10.3.0
-- The CXX compiler identification is GNU 10.3.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: D:/opt/TDM-GCC-64/bin/gcc.exe - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: D:/opt/TDM-GCC-64/bin/c++.exe - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: D:/Projects/CMake-tutorials/build
PS > cmake --build .
[4/4] Linking CXX executable hello_cmake.exe
PS > ./hello_cmake.exe 8
The square root of 8 is 2.8285

此时默认调用 mysqrt 函数,也可以在构建项目时指定 USE_MYMATH 的值为 OFF

1
2
> cmake -DUSE_MYMATH=OFF ..
> cmake --build .

此时会调用自带的 sqrt 函数。

Step 5:添加库的使用要求

使用要求会对库或可执行程序的链接、头文件包含命令行提供了更好的控制,也使 CMake 中目标的传递目标属性更加可控。利用使用要求的主要命令是:

  • target_compile_definitions()
  • target_compile_options()
  • target_include_directories()
  • target_link_libraries()

现在重构一下 Step4 中的代码,使用更加现代的 CMake 方法来包含 MathFunctions 库的头文件。

首先声明,链接 MathFunctions 库的任何可执行文件/库文件都需要包含 MathFunctions 目录作为头文件路径,而 MathFunctions 本身不需要包含,这被称为 INTERFACE 使用要求。

INTERFACE 是指使用者需要、但开发者不需要的那些东西。在 MathFunctions/CMakeLists.txt 最后添加:

1
2
3
4
# MathFunctions/CMakeLists.txt
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)

CMAKE_CURRENT_SOURCE_DIR 表示 MathFunctions 库所在目录。

现在我们已经为 MathFunctions 指定了使用要求 INTERFACE ,那么可以从顶级 CMakeLists.txt 中删除EXTRA_INCLUDES 变量的相关使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if(USE_MYMATH)
add_subdirectory(MathFunctions)
list(APPEND EXTRA_LIBS MathFunctions)
list(APPEND EXTRA_INCLUDES ${PROJECT_SOURCE_DIR}/MathFunctions) # 删除此行
endif()

...

# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
target_include_directories(${PROJECT_NAME} PUBLIC
${PROJECT_BINARY_DIR}
${EXTRA_INCLUDES} # 删除此行
)

现在只要是链接了 MathFunctions 库,就会自动包含 MathFunctions 所在目录的头文件,简洁而优雅。

这里补充两个知识点:

1、使用要求除了 INTERFACE,还有 PRIVATEPUBLICINTERFACE表示使用者需要开发者不需要,PRIVATE表示使用者不需要开发者需要,PUBLIC 表示使用者和开发者都需要。

2、这里使用 add_library 命令生成的 MathFunctions 库其实是 静态链接库 。动态库和静态库的区别是:静态库在 链接阶段 会被链接到最终目标中(比如可执行程序),缺点是同一个静态库如果被不同的程序引用,那么内存中会存在这个静态库函数的多份拷贝。动态库在链接阶段不会被拷贝最终目标中,程序在 运行阶段 才会加载这个动态库。所以多个程序就算引用了同一个动态库,内存中也只存在一份动态库函数的拷贝。

Step 6:build 目录介绍

在文本中,我都是创建了一个 build 用来存放 cmake 构建和编译的产物,这里简单说下里面有些什么东西。

1
2
3
4
5
6
7
8
build/
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
build.ninja
hello_cmake.exe
TutorialConfig.h
MathFunctions/

其中 build.ninjaCMake 根据顶级 CMakeLists.txt 生成的构建文件,通过该文件可以对整个项目进行编译。

hello_cmake.exe 就是生成的可执行文件,通过该文件运行程序。

TutorialConfig.h 是用于配置信息的头文件,是 CMake 根据 TutorialConfig.h.in 文件自动生成的。

还有个 MathFunctions 文件夹:

1
2
3
4
MathFunctions/
CMakeFiles/
cmake_install.cmake
libMathFunctions.a

libMathFunctions.aMathFunctions 静态链接库,可执行文件会通过这个库调用 mysqrt 函数。

P.S. 用过 Linux 的家人们是不是觉得很熟悉呀~

Step 7:生成动态链接库

动态链接库,在Windows下扩展名为 .dll;Linux下为 .so,macOS下为 .dylib

CMake 生成动态库简单实例

修改 MathFunctions/CMakeLists.txt

1
2
3
4
5
6
# MathFunctions/CMakeLists.txt
add_library(MathFunctions SHARED mysqrt.cpp)

target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)

SHARED 表明生成动态链接库。

编译工程,得到的输出如下:

1
2
3
4
5
6
7
8
[main] Building folder: CMake-tutorials 
[build] Starting build
[proc] Executing command: "D:\Program Files\CMake\bin\cmake.EXE" --build d:/Projects/CMake-tutorials/build --config Debug --target all --
[build] [2/4 25% :: 0.083] Building CXX object MathFunctions/CMakeFiles/MathFunctions.dir/mysqrt.cpp.obj
[build] [3/4 50% :: 0.239] Linking CXX shared library MathFunctions\libMathFunctions.dll
[build] [3/4 75% :: 0.445] Building CXX object CMakeFiles/hello_cmake.dir/main.cpp.obj
[build] [4/4 100% :: 1.045] Linking CXX executable hello_cmake.exe
[build] Build finished with exit code 0

此时,我们发现,生成了 libMathFunctions.dll 这个动态链接库~~

需要注意的是,生成的动态链接库必须在程序所在的目录或者是系统搜索目录中,否则程序将无法运行!

CMake 同时构建静态库与动态库

有上面的例子可以看出,使用 ADD_LIBRARY 指令就可以同时构建静态和动态库:

1
2
ADD_LIBRARY(MathFunctions SHARED mysqrt.cpp)
ADD_LIBRARY(MathFunctions STATIC mysqrt.cpp)

但是如果使用这种方式,只会构建一个动态库,不会构建出静态库,虽然静态库的后缀是 .a,此时我们可以修改静态库的名字,这样是可以同时构建动态库和静态库:

1
2
ADD_LIBRARY(MathFunctions SHARED mysqrt.cpp)
ADD_LIBRARY(MathFunctions_static STATIC mysqrt.cpp)

但是我们往往 希望他们的名字是相同的,只是后缀不同 而已,此时可以使用 SET_TARGET_PROPERTIES 指令(该指令详细可见下文 CMake 语法),修改CMakeLists.txt文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
# MathFunctions/CMakeLists.txt
add_library(MathFunctions_static STATIC mysqrt.cpp)

# 对MathFunctions_static的重名为MathFunctions
SET_TARGET_PROPERTIES(MathFunctions_static PROPERTIES OUTPUT_NAME "MathFunctions")
# cmake 在构建一个新的target 时,会尝试清理掉其他使用这个名字的库,如果没有清理还是会只会构建一个动态库,不会构建出静态库
SET_TARGET_PROPERTIES(MathFunctions_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)

add_library(MathFunctions SHARED mysqrt.cpp)

target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)

此时再来编译:

1
2
3
4
5
6
7
8
9
10
[main] Building folder: CMake-tutorials 
[build] Starting build
[proc] Executing command: "D:\Program Files\CMake\bin\cmake.EXE" --build d:/Projects/CMake-tutorials/build --config Debug --target all --
[build] [3/6 16% :: 0.101] Building CXX object MathFunctions/CMakeFiles/MathFunctions_static.dir/mysqrt.cpp.obj
[build] [4/6 33% :: 0.120] Building CXX object MathFunctions/CMakeFiles/MathFunctions.dir/mysqrt.cpp.obj
[build] [5/6 50% :: 0.245] Linking CXX static library MathFunctions\libMathFunctions.a
[build] [5/6 66% :: 0.310] Linking CXX shared library MathFunctions\libMathFunctions.dll
[build] [5/6 83% :: 0.554] Building CXX object CMakeFiles/hello_cmake.dir/main.cpp.obj
[build] [6/6 100% :: 1.144] Linking CXX executable hello_cmake.exe
[build] Build finished with exit code 0

我们就会发现同时生成了静态库和动态库了~~

修改动态库版本号

同时我们还可以修改动态库的版本号

1
2
3
4
// Linux/macOS 一般动态库都有一个版本号的关联
libhello.so.1.2
libhello.so ->libhello.so.1
libhello.so.1->libhello.so.1.2

修改 CMakeLists.txt ,重新构建看看结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$ cmake ..
-- The C compiler identification is GNU 11.3.0
-- The CXX compiler identification is GNU 11.3.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++.exe - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /cygdrive/d/Projects/CMake-tutorials/build
$ make
[ 16%] Building CXX object MathFunctions/CMakeFiles/MathFunctions.dir/mysqrt.cpp.o
[ 33%] Linking CXX shared library cygMathFunctions-1.dll
[ 33%] Built target MathFunctions
[ 50%] Building CXX object CMakeFiles/hello_cmake.dir/main.cpp.o
[ 66%] Linking CXX executable hello_cmake.exe
[ 66%] Built target hello_cmake
[ 83%] Building CXX object MathFunctions/CMakeFiles/MathFunctions_static.dir/mysqrt.cpp.o
[100%] Linking CXX static library libMathFunctions.a
[100%] Built target MathFunctions_static
$ ls
CMakeFiles Makefile cmake_install.cmake cygMathFunctions-1.dll libMathFunctions.a libMathFunctions.dll.a

以上代码在Cygwin环境中编译。可以发现生成的库文件成功的带上了版本号~

安装共享库和头文件

本例中我们将 MathFunctions 的共享库安装到 <prefix>/lib 目录;将 MathFunctions.h 安装到 <prefix>/include/MathFunctions 目录,这样共享库才能够被调用。

修改 CMakeLists.txt 文件:

1
2
3
4
5
6
7
8
...

# 文件放到该目录下
INSTALL(FILES MathFunctions.h DESTINATION include/MathFunctions)

# 二进制,静态库,动态库安装都用TARGETS
# ARCHIVE 特指静态库,LIBRARY 特指动态库,RUNTIME 特指可执行目标二进制。
INSTALL(TARGETS MathFunctions MathFunctions_static LIBRARY DESTINATION lib ARCHIVE DESTINATION lib RUNTIME DESTINATION lib)

此时,编译安装输出如下(Cygwin环境):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ cmake -DCMAKE_INSTALL_PREFIX=/usr ..
-- Configuring done
-- Generating done
-- Build files have been written to: /cygdrive/d/Projects/CMake-tutorials/build
$ cmake --build .
Consolidate compiler generated dependencies of target MathFunctions
[ 16%] Linking CXX shared library cygMathFunctions.dll
[ 33%] Built target MathFunctions
Consolidate compiler generated dependencies of target hello_cmake
[ 50%] Linking CXX executable hello_cmake.exe
[ 66%] Built target hello_cmake
Consolidate compiler generated dependencies of target MathFunctions_static
[100%] Built target MathFunctions_static
$ cmake --install .
-- Install configuration: ""
-- Installing: /usr/include/MathFunctions/MathFunctions.h
-- Installing: /usr/lib/libMathFunctions.dll.a
-- Installing: /usr/lib/cygMathFunctions.dll
-- Installing: /usr/lib/libMathFunctions.a

测试一下运行情况:

1
2
3
$ export PATH=$PATH:/usr/lib
$ ./hello_cmake.exe 123
The square root of 123 is 11.0906

成功啦~

使用外部动态库和头文件

CMakeLists.txt 配置如下:

1
2
3
INCLUDE_DIRECTORIES(/usr/include/MathFunctions)
ADD_EXECUTABLE(${PROJECT_NAME} main.cpp)
TARGET_LINK_LIBRARIES(hello cygMathFunctions.dll)

使用外部静态库

配置如下:

1
2
3
INCLUDE_DIRECTORIES(/usr/include/MathFunctions)
ADD_EXECUTABLE(${PROJECT_NAME} main.cpp)
TARGET_LINK_LIBRARIES(main libMathFunctions.a)

Step 7:CMake常用的命令或函数:

  • 定义项目:

    project(myProject C CXX):该命令会影响 PROJECT_SOURCE_DIRPROJECT_BINARY_DIRPROJECT_NAME等变量。另外要注意的是,对于多个project嵌套的情况,CMAKE_PROJECT_NAME是当前CMakeLists.txt文件回溯至最顶层CMakeLists.txt文件中所在位置之前所定义的最后一个project的名字。

    cmake_minimum_required(VERSION 3.0):出进行编译所需要的CMake最低版本,如果不指定的话系统会自己指定一个,但是也会扔出一个warning

  • 搜索源文件:

    file(<GLOB|GLOB_RECURSE> <variable> <pattern>):按照正则表达式搜索路径下的文件,比如:file(GLOB SRC_LIST "./src/*.cpp")

    aux_source_directory(<dir> <variable>):搜索文件内所有的源文件。

  • 添加编译目标:

    add_library(mylib [STATIC|SHARED] ${SRC_LIST})

    add_executable(myexe ${SRC_LIST})

  • 添加头文件目录:

    include_directories(<items>):为该位置之后的target链接头文件目录(不推荐)。

    target_include_directories(<target> <PUBLIC|INTERFACE|PRIVATE]> <items>):为特定的目标链接头文件目录。

  • 添加依赖库:

    link_libraries(<items>):为该位置之后的target链接依赖库。

    target_link_libraries(<target> <items>):为特定的目标链接依赖库。
    这里,常见的依赖库可能是以下几种情况:

    1.  在此次编译的工程里添加的目标,给出目标名;
    2.  外部库,给出路径和库文件全名;
    3.  外部库,通过`find_package()`等命令搜索到的。
    

    对于find_package(XXX),该命令本身并不直接去进行搜索,而是通过特定路径下的FindXXX.cmake或XXXConfig.cmake文件来定位头文件和库文件的位置,分别被称为Module模式和Config模式。该命令会定义一个XXX_FOUND变量,如果成功找到,该变量为真,同时会定义XXX_INCLUDE_DIRXXX_LIBRARIES两个变量,用于link和include。

    add_dependencies( []…):

    使顶层 <target> 依赖于其他顶层目标,以确保它们在 <target> 之前构建。顶级目标是由 add_executable()add_library()add_custom_target() 命令之一创建的目标(但不是由CMake生成的目标,如 install )。

  • 添加子目录:

    add_subdirectories(<dir>):子目录中要有CMakeLists.txt文件,否则会报错。

  • 包含其他cmake文件:

    include(./path/to/tool.cmake)

    set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ./path/to),随后include(tool)
    该命令相当于将tool.cmake的内容直接包含进来。

  • 定义变量:

    set(<variable> <value>... [PARENT_SCOPE])

    set(<variable> <value>... CACHE <type> <docstring> [FORCE])

    其中CACHE会将变量定义在缓存文件CMakeCache.txt里,可以在下次编译的时候读取。

  • 作用域:

    add_subdirectories(<dir>)会创建一个子作用域,里面可以使用父作用域里定义的变量,但里面定义的变量在父作用域不可见,同样,在子作用域修改父作用域里的变量不会影响父作用域。function()同样会产生一个子作用域。若想让子作用域里的定义或者修改在父作用域可见,需要使用PARENT_SCOPE标记。

    相对地,macro()include()不会产生子作用域。

  • 选项:

    add_option(MY_OPTION <ON|OFF>):会定义一个选项。在使用cmake命令时,可以通过-D改变选项的值。比如cmake .. -DMY_OPTION=ON

  • 编译选项:

    add_compile_options(-std=c++11)

    如果想要指定具体的编译器的选项,可以使用make_cxx_flags()cmake_c_flags()

  • 与源文件的交互:

    configure_file(XXX.in XXX.XX)会读入一个文件,处理后输入到新的位置。一方面,会替换掉#XXX或者@XXX@定义的内容。另一方面,会将文件里的#cmakedefine VAR …替换为#define VAR …或者/* #undef VAR */

  • 字符串操作、循环、判断、文件/变量存在判断等

    这些命令同样有用,请参考网络资料。

参考文献:

[1] https://zhuanlan.zhihu.com/p/500002865

[2] https://blog.csdn.net/weixin_45004203/article/details/125256367

[3] https://blog.csdn.net/LearnLHC/article/details/125515650

TXT 自动分章、排序小工具

TXT 自动分章、排序~

GitHub forks GitHub stars GitHub license GitHub issues

前言

由于我喜欢看网文,但又不想付…, 所以从各种网站爬虫下载TXT。

但是…有很多都是乱序的喵!>_<

于是,我在伟大的度娘上找呀找…发现并没有能用的软件。

所以,便有了这个!

软件截图

已知问题 Known issues.

  • 仅能处理部分格式的章节——章节后面有一个空格的那种
  • 章节序号是汉字的不能处理 >_< 正在测试中!!
  • ……

贡献 How to contribute?

欢迎大家在GitHub提出issue和pullrequest!
会尽可能在第一时间处理~~

Final

Provide by Yinan Qin with ♥

下载

GitHub Release

QuarkOS_1.0.0beta1 发行说明

QuarkOS v1.0.0 beta1 发行说明

说明

这是QuarkOS的第一个发行版,基于Deepin v23与Debian bullseye制作。

内涵如下软件:

  • 星火应用商店
  • 微信
  • 网易云音乐
  • Chromium

已知问题

  1. 在efi平台上安装需要使用ventoy制作启动盘,并且在安装成功后需要额外配置grub,此操作需要网络连接:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    $ for i in dev proc sys; do mount --rbind /$i /mnt/$i; done
    $ cp /etc/resolv.conf /mnt/etc/resolv.conf
    $ chroot /mnt /bin/bash
    $(chroot) apt purge grub-pc grub-efi grub-efi-amd64
    $(chroot) rm -r /boot/grub
    $(chroot) apt install grub-efi-amd64
    $(chroot) exit
    $ for i in dev/pts dev/ proc sys; do umount -lf /mnt/$i; done
    $ rm /mnt/etc/resolv.conf

    下载链接

Qinyn NTP (网络授时) 服务使用说明

服务总览

我们目前提供一台时间服务器:

1
ntp.300c.top

服务器位于成都,提供 IPv4 服务。

服务介绍

NTP (网络时间协议, network time protocol) 是网络中保持时间同步的协议 (How does it work)。简单来说,客户端通过向服务器发送带有时间戳的数据包和服务器回复带有时间戳的数据包,来获得客户端发送时间,服务端接收时间、服务端发送时间、客户端接收时间 4 个时间戳。客户端系统时间偏移量定义为 δ = (t₃ - t₀) - (t₂ - t₁)。如果运行 ntpd 服务,一般来说 ntpd 会逐渐调整时钟,避免时间跳变。这对于运行计费系统、交易系统或者其他对时间准确性和事件先后顺序敏感的操作十分重要。

Linux 客户端配置

使用 systemd-timesyncd 的用户需要修改 /etc/systemd/timesyncd.conf,将其中 NTP= 一行取消注释,修改为NTP=ntp.300c.top。修改好后,可使用 systemctl restart systemd-timesyncd 使配置生效。

使用 ntpd 的用户需要在 /etc/ntp.conf 中添加一行 server ntp.300c.top 。(若您的发行版使用 Chrony,请修改对应的配置文件 /etc/chrony.conf。)

为了确保 ntpd 服务正在运行,使用你的发行版的 initscripts 脚本或 systemctl(若有)进行检查和修正。

如果你的机器的时钟发生跳变不会有严重后果 (例如在你的笔记本上),你可以使用 sudo ntpdate ntp.300c.top 进行一次性的同步。

Mac 客户端配置

在“系统配置 > 日期与时间 > 自动设置日期与时间”一栏,填入 ntp.300c.top

在 macOS Mojave 及更新的系统,你可以使用 sudo sntp -sS ntp.300c.top 来进行一次性的同步,否则,使用sudo ntpdate ntp.300c.top进行同步。

Windows 客户端配置

Windows XP 及以下版本的配置方式可以参看上海大学 NTP 网站上提供的教程

Windows 10 客户端配置

在“控制面板 > 时钟、语言和区域 > 日期和时间 > Internet时间 > 更改设置”中勾选“与 Internet 时间服务器同步”,在“服务器”一栏填入 ntp.300c.top

您也可以通过在命令提示符中使用 w32tm /config /manualpeerlist:ntp.300c.top /syncfromflags:manual /update 来将此服务器设置为您的时间服务器.