C++源码生成·第二章·构建项目
1 概述
书接上回,在「C++源码生成·第一章·初试模板」中展示了通过复制模板文件进行源码生成的方案,同时也描述了使用模板文件在源码生成工具中的作用,包括:提高代码一致性、减少重复劳动、易于维护和更新、增强可扩展性、促进最佳实践等优点。在本章将继续完善 Python 脚本,通过代替模板中的关键字来发挥模板文件的真正作用,实现源码的真正动态生成功能。
2 创建项目
2.1 命令行接收项目名称
在命令行中接收项目名称作为参数可以自动化项目的创建过程,命令行接口允许用户根据需要输入项目名称,这提供了极大的灵活性。在「Python 处理命令行参数」一文中,展示了在 Python 脚本中如何使用 argparse
模块来处理命令行参数。
在本项目中我们使用 argparse
模块来处理命令行参数,命令行的用法如下:
./cppgen <project_path>
其中的 <project_path>
指项目名称的路径,是必填参数。Python 脚本根据项目路径创建目录,并且使用路径的文件名作为项目名称,比如:./cppgen /path/to/hello
,则项目名称为 hello
。
以下 Python 脚本展示了如何获取命令行中的项目名称:
cppgen
#!/usr/bin/python3
import argparse
def main():
# Create an ArgumentParser object
parser = argparse.ArgumentParser(description="cpp source code generator")
# Add positional arguments
parser.add_argument('project_path', type=str, help='to be generated project path')
# Parse the command-line arguments
args = parser.parse_args()
# Handle args.project_path
print(f"project path: {args.project_path}")
if __name__ == "__main__":
main()
在以上 Python 脚本中:
- 我们创建了一个
ArgumentParser
对象,并为其提供了描述信息。 - 使用
add_argument()
方法添加了一个参数:project_path
项目路径,这是一个必填参数,必须由用户输入。
- 调用
parse_args()
方法解析命令行参数,并将结果存储在args
对象中。 - 解析后的参数存储在
args.project_path
中,示例中我们将其打印出来。
argparse
模块自动提供了命令行处理方法的帮助信息与错误处理逻辑,可以使用 -h
参数查看:
./cppgen -h
结果显示如下:
usage: cppgen [-h] project_path
cpp source code generator
positional arguments:
project_path to be generated project path
optional arguments:
-h, --help show this help message and exit
如果输入项目名称,可以使用以下命令:
./cppgen hello
这将输出:
project path: hello
2.2 创建项目目录
通过 {args.project_path}
我们可以获取命令行参数输入进来的项目路径名称,我们创建一个 generate_source_code()
方法根据项目路径名称创建项目的完整目录结构。
import re
from pathlib import Path
def is_valid_name(name):
# Define the regular expression pattern for validation
pattern = r'^[A-Za-z][A-Za-z0-9_.-]*$'
match = re.match(pattern, name)
return match is not None
def generate_source_code(project_path):
project_name = Path(project_path).name
if (not is_valid_name(project_name)):
print(f"failed: '{project_name}' is invalid project name")
sys.exit(1)
try:
os.mkdir(project_path)
print(f"project '{project_path}' created")
source_path = project_path + '/src'
os.mkdir(source_path)
print(f"'{source_path}' created")
except OSError as e:
print(f"failed: {e}")
sys.exit(1)
在以上 Python 脚本中:
- 创建一个表示由
project_name
变量指定的路径的Path
对象,并获取这个路径中的最后一部分(文件名或目录名)。 - 我们需要先从
pathlib
模块中导入Path
类,例如通过from pathlib import Path
。 - 在
is_valid_name
方法中定义了一个正则表达式,校验一个项目名称是否符合特定的规则(包含字母与数字,以及特定的符号如 ‘-’ 和 ‘_’ 以及 ‘.’,并且首字符必须为大小写字母)。如 “hello_2.7” 为正常的项目名称,而 “1a^@#” 包含非法字符,则判定为不可用名称。 - 使用
re.match
来检查字符串是否符合模式,如果match
不是None
,则表示字符串符合规则。需要使用re
模块来实现,通过import re
导入。 - 使用
os
模块,os
模块提供了与操作系统交互的功能,例如创建目录。我们使用os.mkdir()
函数创建一个单独的目录,避免覆盖已有的项目目录。 - 创建完项目目录后,接着创建
src
目录用于存放源码文件,比如main.cpp
文件。
2.3 目录结构
项目一般会包含多个文件或目录,比如项目的说明文档 README.md
,或者 LICENSE
文件,存储源码的 src
目录,存储项目文档资料的 doc
目录,又或者是存储测试源码的 test
目录等。
在这里我提供了一个简单的目录结构示例:
hello
├── src/ # 存放开发源码
│ └── main.cpp # 主程序文件
├── README.md # 项目情况说明文档
└── CMakeList.txt # CMake 编译系统配置文件
结合「C++源码生成·第一章·初试模板」提到的模板的用法,为了实现以上目录结构中展示的文件,需要有 main.cpp.tmpl
,README.md.tmpl
,CMakeList.txt.tmpl
三个模板文件。
由于我们现在已经能够获取到项目名称,同时 Python 提供了字符串替换的方法 string.replace()
,我们可以通过字符串替换的方法将模板中的关键字进行替换,达成项目的实际需求。
2.4 替换模板文件关键字
如果模板文件中有多个需要替换的字符或字符串,可以通过定义一个替换项的列表,然后遍历这个列表来逐一替换文件中的内容。以下是一个扩展的 Python 脚本示例,它展示了如何替换模板文件中的多个字符或字符串:
def replace_multiple_in_file(file_path, replacements, output_file_path=None):
try:
# Read the contents of the file
with open(file_path, 'r', encoding='utf-8') as file:
file_contents = file.read()
# Apply each replacement in sequence
for old, new in replacements:
file_contents = file_contents.replace(old, new)
# Determine the output file path
if output_file_path is None:
output_file_path = file_path
# Write the modified contents to the output file
with open(output_file_path, 'w', encoding='utf-8') as file:
file.write(file_contents)
print(f"saved to '{output_file_path}'")
except FileNotFoundError:
print(f"error: The file {file_path} was not found.")
except Exception as e:
print(f"an error occurred: {e}")
在上文提到的 generate_source_code
后面调用 replace_multiple_in_file
方法:
def generate_source_code(project_path)
...
templates = [
('template/CMakeLists.txt.tmpl', project_path + '/CMakeLists.txt'),
('template/README.md.tmpl', project_path + '/README.md'),
('template/main.cpp.tmpl', source_path + '/main.cpp')
]
replacements = [
('${project}', project_name)
]
for input_template, output_template in templates:
replace_multiple_in_file(input_template, replacements, output_template)
在这段 Python 脚本中,replacements
是一个包含元组的列表,每个元组都包含要替换的旧字符串和新字符串。脚本会遍历这个列表,并逐一应用替换操作。在 replace_multiple_in_file
方法中需要指定源文件与目标文件,templates
也是一个包含元组的列表,每个元组都包含了模板文件与目标文件的路径,在脚本的末尾,for
语句会遍历 templates
并逐一调用替换方法。
最后在 main()
函数中调用 generate_source_code()
完成替换模板文件中的关键字实现模板应用的效果。
3 生成项目
以下是项目生成与编译的过程:
-
将以上的 Python 脚本保存成
cppgen
-
通过
chmod +x cppgen
修改脚本权限为可执行权限。 -
运行命令行时带上项目名称作为参数:
./cppgen hello
-
此时会生成
hello
项目目录。 -
执行
cd
命令进入hello
目录会看到如 2.3 节介绍的目录结构。 -
创建临时构建文件夹
build
。 -
在
build
目录中执行cmake ..
指令生成 Makefile 文件。cmake ..
此步骤要求系统中已提交安装好 CMake 构建系统,如果在 Ubuntu 中可通过以下指令安装 CMake:
sudo apt install -y cmake
-
cmake 会根据
CMakeList.txt
文件生成Makefile
文件,然后可以使用make
指令进行编译:make
-
以上步骤的完整指令如下:
./cppgen hello cd hello mkdir build cmake .. make
4 运行结果
执行 make
指令将编译 src
目录下的所有源码文件并生成名为 hello
的可执行文件,运行该文件会输出一段带有项目名称的信息:
$ ./hello
Demonstrate using template for hello
程序运行后会进入循环休眠状态直到收到退出信号。如果按下 Ctrl
-C
终止运行,进程将捕获信号并打印以下内容:
^CCaught signal number 2 (Interrupt)
如果使用 kill <pid>
(<pid>
为 main 的进程号,可通过 pgrep main
获取)指令,进程将捕获 SIGTERM 信号(值为 15),并打印如下信息:
Caught signal number 15 (Terminated)
5 总结
在本文中展示了通过设计模板文件与使用项目名称替换其中关键字的方法,实现动态生成项目源码与项目说明文档的功能,展示了自动化源码生成工具的强大作用。简化了项目开发流程,提升了项目开发速度。
本章内容与 github 是联动的,对应的 tag 是 v1.2.2。关于 git tag
的用法与说明可以参考我的另一篇文章「git tag 用法」。
在下一章会介绍通过增强项目模板的方式,将 C++ 开发过程中的最佳实践进行沉淀,自动生成优秀的源码,提升 C++ 开发能力,让自动化源码生成工具发挥更大作用。敬请关注!