CMake 项目生成脚本

C++ 比较尴尬的一点就是缺少比较“傻瓜”的工具库,不得不依靠第三方来补充。想快速开始写个小的 Demo 的时候光找库就花去不少时间。于是糊了一个脚本去自动生成这些基础的东西。放在这里方便自己以后参考。

用到的库列表:

  • {fmt}: 字符串格式化库
  • spdlog: 日志
  • backward-cpp: 崩溃时的堆栈输出
  • benchmark: 快速性能测试
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
#!/bin/bash

print_help() {
echo "Usage:"
echo " cproject <type> <name>"
echo "Types:"
echo " bin, bench"
}

die() {
echo "Unexpected error"
exit 2
}

# Ninja does not work with backward-cpp
new_bin() {
local name="$1"
mkdir "$name" || die
cd "$name" || die
git clone 'https://github.com/fmtlib/fmt.git' third_party/fmt || die
git clone 'https://github.com/bombela/backward-cpp.git' third_party/backward-cpp || die
git clone 'https://github.com/gabime/spdlog.git' third_party/spdlog || die
cat << EOF > CMakeLists.txt
cmake_minimum_required(VERSION 3.11)
project(${name})

if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
endif()

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_CXX_FLAGS "\${CMAKE_CXX_FLAGS} -Wall -Wextra")
set(CMAKE_CXX_FLAGS_DEBUG "\${CMAKE_CXX_FLAGS_DEBUG} -ggdb -O0")
set(CMAKE_EXE_LINKER_FLAGS "\${CMAKE_EXE_LINKER_FLAGS} -rdynamic")

add_subdirectory(third_party/fmt)
add_subdirectory(third_party/spdlog)
add_subdirectory(third_party/backward-cpp)

add_executable(main)
target_sources(main PRIVATE main.cpp \${BACKWARD_ENABLE})
target_link_libraries(main PRIVATE fmt spdlog -ldw)
EOF

cat << 'EOF' > main.cpp
#include <iostream>
#include <string>
#include "fmt/format.h"
#include "spdlog/spdlog.h"

#define CRASH(...) \
{ ::spdlog::critical(__VA_ARGS__); \
int _ [[maybe_unused]] = \
*reinterpret_cast<int*>(0); }

inline int get_return_code() {
CRASH("unimplemented function: {}", __func__);
return 0;
}

int main(int argc, char* argv[]) {
fmt::print("{}, {}\n\n", "hello", "world");

spdlog::set_level(spdlog::level::debug);
spdlog::debug("debug message");
spdlog::info("info message");
spdlog::warn("warning message");
spdlog::error("Some error message with arg: {}", 1);
return get_return_code();
}
EOF
echo
echo "Skeleton generated. Run the command and you shall see " \
"the stacktrace demo."
echo " cd ${name} && cmake -B build && " \
"make -C build -j main && build/main"
}

new_bench() {
local name="$1"
mkdir "$name" || die
cd "$name" || die
git clone 'https://github.com/fmtlib/fmt.git' third_party/fmt || die
git clone 'https://github.com/bombela/backward-cpp.git' third_party/backward-cpp || die
git clone 'https://github.com/gabime/spdlog.git' third_party/spdlog || die
git clone 'https://github.com/google/benchmark.git' third_party/benchmark || die
git clone 'https://github.com/google/googletest.git' third_party/benchmark/googletest || die
cat << EOF > CMakeLists.txt
cmake_minimum_required(VERSION 3.11)
project(${name})

if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_CXX_FLAGS "\${CMAKE_CXX_FLAGS} -Wall -Wextra -g")
set(CMAKE_EXE_LINKER_FLAGS "\${CMAKE_EXE_LINKER_FLAGS} -rdynamic")

set(BENCHMARK_ENABLE_LTO true)

add_subdirectory(third_party/fmt)
add_subdirectory(third_party/spdlog)
add_subdirectory(third_party/backward-cpp)
add_subdirectory(third_party/benchmark)

add_executable(bench)
target_sources(bench PRIVATE bench.cpp \${BACKWARD_ENABLE})
target_link_libraries(bench PRIVATE fmt spdlog benchmark::benchmark_main -ldw)
EOF

cat << 'EOF' > bench.cpp
#include "benchmark/benchmark.h"
#include <cinttypes>
#include <cstdlib>

constexpr size_t ARRAY_LEN = 1024*64;
static void BM_SeqArrayAccess(benchmark::State& state) {
int stride = state.range(0);
uint64_t arr[ARRAY_LEN];
for (auto _ : state) {
for (size_t idx = 0; idx < ARRAY_LEN; idx += stride) {
arr[idx] = random();
}
}
}
BENCHMARK(BM_SeqArrayAccess)
->Arg(1)
->Arg(2)
->Arg(3)
->Arg(4)
->Arg(5)
->Arg(6)
->Arg(7)
->Arg(8);
EOF
echo
echo "Skeleton generated."
echo " cd ${name} && cmake -B build && " \
"make -C build -j bench && build/bench"
echo "You may also need to change your CPU scheduler:"
echo " sudo cpupower frequency-set -g performance"
}

if [[ "$2" == "" ]]; then
echo "Missing project name"
echo
print_help
exit 1
fi

if [[ -e "$2" ]]; then
echo "File \"$2\" already exists"
exit 1
fi

if [[ "$1" == "bin" ]]; then
new_bin "$2"
elif [[ "$1" == "bench" ]]; then
new_bench "$2"
else
echo "Unknown type: $1"
echo
print_help
exit 1
fi

简易内核开发环境

近期尝试了一下写 Linux 内核代码,于是把折腾过程记录一下,方便以后参考。本文默认使用 x86_64 架构以及 Linux 作为宿主系统。

使用 QEMU 运行 Linux 内核

在开始写代码之前,我们需要先准备好模拟器用于运行 Linux。我这里用了QEMU,你也可以用 VirtualBox 之类的虚拟机。一般来说,我们需要一个内核可执行文件(内核本体)和一个 initramfs 镜像(提供用户态程序)。当内核被加载后,它会自动载入 initramfs 镜像,并执行其中的/init程序。由于这一节的重点是 QEMU 配置,所以我直接使用宿主机的内核和 initramfs。以我的 Archlinux 系统为例,内核文件是/boot/vmlinuz-linux,initramfs 文件是/boot/initramfs-linux.img。使用以下命令启动 QEMU,按Ctrl-A x退出。

1
2
3
4
5
6
7
qemu-system-x86_64 \
-kernel /boot/vmlinuz-linux \
-initrd /boot/initramfs-linux.img \
-nographic -append "console=ttyS0" \
-m 512 \
--enable-kvm \
-cpu host
  • -kernel 指定内核可执行文件。
  • -initrd 指定 initramfs disk 镜像文件。
  • -nographic -append "console=ttyS0" 禁用视频输出并使用串口作为终端。
  • -m 512 设定内存。
  • --enable-kvm 使用KVM。
  • -cpu host 使用宿主机的 CPU 特性。

你应该能看到一些系统启动的信息以及无法挂载根分区的报错,这是正常现象,因为我们没有提供任何磁盘文件。你应该可以进入一个紧急修复 Shell, 执行一些简单的如 ls cd 之类的命令。

Haskell 简易指南

安装

用任何你喜欢的方法安装 Glasgow Haskell Compiler (a.k.a. GHC)。Cabal 之类的
依赖管理系统就用不着了。 因为我也不会用。 保证能够执行ghcghci命令就行。

GHCi基础

首先,把以下文件保存成helloworld.hs

helloworld.hs
1
foo = "hello, world"

然后执行ghci helloworld.hs,然后在>提示符后输入foo并回车

1
2
3
4
5
6
GHCi, version 8.6.3: http://www.haskell.org/ghc/  :? for help
[1 of 1] Compiling Main ( helloworld.hs, interpreted )
Ok, one module loaded.
*Main> foo
"hello, world"
*Main>

你可以使用:r来重新载入文件,也可以使用:l <文件名>来载入代码。

编写一个简易的Chrome扩展

时隔半年的毫无诚心的流水帐作品。
假设读者有基础的Javascript能力。

起因

常去的某资源站的某资源发布者喜欢把重要的内容加上花里胡哨的特殊效果并藏在页面的角落里。
虽说要尊重资源的发布者,不过这种给人添堵的行为实在令我感到不爽,于是研究了一下Chrome的扩展程序(Extension)。

基本思路

要干的事情有两件:

  1. 将内容移到页面的显著位置
  2. 去掉辣眼睛的特殊效果 好吧,其实这条并不重要,毕竟内容已经被移到显著位置了

很自然的,直接用Javascript操纵DOM树即可实现希望的效果。
那么要怎么自动载入脚本呢?

借助IPsec和strongSwan建立隧道并分配IPv6地址

准备

在一年前,我写过一篇文章,介绍利用GRE隧道将一台服务器的IPv6地址“分配”给另一台电脑,令其能访问IPv6网络的方法。
不过那种方法存在一些问题:

  • 不能通过NAT
  • 数据不加密
  • 需要在服务器手动更新IP

于是热爱折腾作死的我研究了一下使用IPsec配合IKEv2对流量进行加密的方法。

服务器与本地均为ArchLinux(Arch大法好),strongSwan软件包可从AUR安装。
服务器需要至少有一个公网IPv4和一段Routed IPv6 Subnet。

Linux环境TCP Socket与Epoll使用备忘

流水帐式地记录了 Linux 下 TCP Socket 通信的方法和基本的 Epoll 使用方法。
没有错误处理。

地址解析

1
2
3
4
struct addrinfo *listen_addr; //存放解析结果。参见`man getaddrinfo`
getaddrinfo("0.0.0.0", "55553", NULL, &listen_addr); // getaddrinfo([主机名],[端口],[hint],[结果])。成功返回 `0`
// ...
freeaddrinfo(listen_addr); //释放资源,返回void

监听

这种方式只能同时处理一个连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int fd = socket(AF_INET, SOCK_STREAM, 0); // int socket(int domain, int type, int protocol); 参见`man 3 socket` 创建文件描述符, 出错返回-1
bind(fd, listen_addr->ai_addr, listen_addr->ai_addrlen);
// int bind(int socket, const struct sockaddr *address,socklen_t address_len);
// 绑定地址,出错返回-1,参见`man 3 bind`
listen(fd, SOMAXCONN);
// int listen(int fd, int backlog(最大队列长度))
// 开始监听,出错返回-1
while (1) {
int new_fd = accept(fd, NULL, NULL);
// accept([fd], [监听地址], [监听地址结构体长度]) 第2,3个参数同bind()
// 接受连接请求,若无请求则阻塞(也有可能是EAGAIN,取决于你需要什么)
// 返回用于和对端通信的新的文件描述符,出错返回-1
// ... handle(new_fd);
close(new_fd); // 关闭文件描述符
}