为 Meson 做贡献
Meson 的很大一部分是由核心团队之外的人贡献的。本文档解释了 Meson 的一些设计原理,以及如何创建和提交你的补丁以包含在 Meson 中。
感谢您对参与开发的兴趣。
提交补丁
所有更改必须作为 GitHub 上的拉取请求 提交。这会导致它们通过 CI 系统运行。所有提交都必须通过完整的 CI 测试运行,然后才能考虑提交。
保持拉取请求更新
当您的拉取请求正在审查时,可能会将其他更改提交到 master,导致必须解决的合并冲突。对此的基本规则非常简单:仅使用 rebase 保持您的拉取请求更新。
不要将 head 合并回您的分支。拉取请求中的任何合并提交都会使其无法合并到 master,您必须删除它们。
新功能的特殊程序
每个新功能都需要一些额外的步骤,即
- 必须在
test cases/
下包含一个项目测试,或者如果不可能或测试需要特殊环境,则必须将其放入run_unittests.py
中。 - 必须使用 FeatureChecks 框架 注册,该框架会在您尝试针对较旧的 Meson 版本使用新功能时向用户发出警告。
- 需要在
docs/markdown/snippets/
内包含一个发行说明片段,其中包含标题和简短段落,解释该功能的作用以及示例。
接受和合并
任何合并提案获得的审查和接受类型取决于它包含的更改。所有拉取请求都必须由具有提交权限且不是原始提交者的人员进行审查和接受。合并请求可以大致分为三个不同的类别。
第一个类别包括仅更改 docs/markdown
下的 markdown 文档的 MR。任何有访问权限的人都可以直接将更改推送到 master。对于重大更改,仍然建议创建 MR,以便其他人可以对此进行评论。
第二组包括不会更改任何功能的合并、CI 系统的修复以及已添加回归测试(见下文)并且不会更改现有功能的错误修复。一旦成功审查,任何具有合并权限的人都可以将这些合并到 master。
最后一种合并是那些添加新功能或以向后不兼容的方式更改现有功能的合并。这些需要项目负责人的批准。
以简化的列表形式,拆分将如下所示
- 具有提交访问权限的成员可以执行
- 文档更改(如果需要,可以直接推送到 master)
- 不会更改功能的错误修复
- 重构
- 新的依赖项类型
- 新的工具支持(例如,新的 Doxygen 类工具)
- 对现有语言的新编译器的支持
- 项目负责人决定是必要的
- 新模块
- Meson 语言中的新函数
- Meson 文件的语法更改
- 破坏向后兼容性的更改
- 对新语言的支持
绿色 CI 运行对于合并是强制性的
在合并请求具有完全绿色的 CI 运行之前,任何合并请求都不能合并。CI 失败的原因无关紧要,这是一个硬性阻塞器。即使 MR 可能与失败无关,并且显然应该允许,它也不能合并。只有修复 CI 问题的 MR 才允许登陆到主干。
只有一个例外。在撰写本文时,Apple CI 不稳定,有时会因时钟偏移错误而失败。
如果合并导致 CI 失败,任何开发人员都可以将其从 master 中还原。然后,原始提交者有责任重新提交修正版本。
将拉取请求合并到主干的策略
Meson 的合并策略应符合以下准则
-
尽可能保留历史记录
-
仓库中尽可能少的垃圾
-
"master 血统" 中的所有内容都应该始终通过所有测试
这些目标略有矛盾,因此执行正确操作通常需要执行合并操作的人员进行一些判断。GitHub 提供三种不同的合并选项,选择它们之间的经验法则如下
-
单提交拉取请求应始终被 rebase
-
具有一个提交和一个“修复”提交的拉取请求(例如,测试某件事是否通过 CI)应该被压缩
-
具有许多提交的大分支应使用合并提交合并,尤其是在其中一个提交未通过所有测试的情况下(例如,大型且困难的重构)
如有疑问,请在 IRC 上寻求指导。
测试
所有新功能都必须附带自动测试,这些测试可以彻底证明该功能按预期工作。同样,错误修复必须附带一个单元测试,该测试演示错误,证明它已修复,并防止该功能在将来中断。
有时很难为给定错误创建单元测试。如果是这种情况,请在您的拉取请求中注明。在这种情况下,我们可能会允许错误修复合并请求。这是根据具体情况进行的。有时,编写测试可能比说服维护人员不需要测试更容易。运用判断力,并在出现问题的情况下寻求帮助。
测试分为两个不同的部分:单元测试和完整项目测试。要运行所有测试,请执行 ./run_tests.py
。单元测试可以使用 ./run_unittests.py
运行,项目测试可以使用 ./run_project_tests.py
运行。
项目测试
可以使用 ./run_project_tests.py --only
选项选择项目测试的子集。当只测试 Meson 的某个特定部分时,这可以节省大量时间。例如,对 Meson 有用的、简单的贡献是确保支持完整的编译器集。例如,可以设置 FC=ifort
、FC=flang
或 FC=flang-new
或类似的方式使用 ./run_project_test.py --only fortran
测试各种 Fortran 编译器。一些测试族需要特定的后端才能运行。例如,所有 CUDA 项目测试都通过 ./run_project_tests.py --only cuda --backend ninja
在 Windows 上运行并通过
每个项目测试都是一个独立的项目,可以独立编译。它们都在 test cases
子目录中。运行单个项目测试的最简单方法是执行类似 ./meson.py test\ cases/common/1\ trivial builddir
的操作。唯一的例外是下面讨论的 test cases/unit
目录。
common
子目录中的测试用例旨在始终针对所有后端运行。它们应该只依赖于 C 和 C++,而不依赖任何外部依赖项,例如库。需要这些库的测试位于 test cases/frameworks
目录中。如果 common
目录中需要外部程序,例如代码生成器,则应将其实现为 Python 脚本。测试项目的目的是也提供示例项目,最终用户可以将其用作其自身项目的基线。
所有项目测试都遵循相同的模式:它们被配置、编译、测试被运行,最后安装被运行。通过意味着配置、构建和测试成功,并且安装的文件与预期的文件匹配。
任何需要更彻底分析的测试,例如检查命令行中是否可以找到某些编译器参数或生成的 pkg-config 文件是否真正有效,都应该使用单元测试完成。
此外
-
crossfile.ini
和nativefile.ini
分别使用--cross-file
和--native-file
选项传递给配置步骤。 -
mlog.cmd_ci_include()
可以从 Meson 中的任何地方调用,以将附加文件的内容捕获到 CI 日志中(如果失败)。
单元测试所需的项目位于 test cases/unit
子目录中。它们不会作为 ./run_project_tests.py
的一部分运行。
配置项目测试
测试用例根目录中的(可选)test.json
文件用于配置测试。test.json
中的所有以下根条目都是相互独立的,可以根据需要组合。
示例 test.json
{
"env": {
"VAR": "VAL"
},
"installed": [
{ "type": "exe", "file": "usr/bin/testexe" },
{ "type": "pdb", "file": "usr/bin/testexe" },
{ "type": "shared_lib", "file": "usr/lib/z", "version": "1.2.3" },
],
"matrix": {
"options": {
"opt1": [
{ "val": "abc" },
{ "val": "qwert" },
{ "val": "bad" }
],
"opt2": [
{ "val": null },
{ "val": "true" },
{ "val": "false" },
]
},
"exclude": [
{ "opt1": "qwert", "opt2": "false" },
{ "opt1": "bad" }
]
},
"tools": {
"cmake": ">=3.11"
}
}
env
env
键包含一个字典,该字典指定在测试的配置步骤期间要设置的附加环境变量。
对使用 @<VAR>@
语法配置字符串有一些基本支持
-
@ROOT@
:源目录的绝对路径 -
@PATH@
:PATH
环境变量的当前值
installed
installed
字典包含一个字典列表,描述了哪些文件预计将被安装。每个字典都包含以下键
file
type
-
platform
(可选) -
version
(可选) -
language
(可选)
file
条目包含实际安装文件的相对路径(从安装根目录)。
type
条目指定了根据当前平台如何解释 file
路径。目前支持以下值
type | Description |
---|---|
file
|
无后处理,只使用提供的路径 |
python_file
|
使用提供的路径,同时替换 python 目录。 |
dir
|
要包含目录内的所有文件(用于生成的文档等)。该路径必须是一个有效的目录 |
exe
|
用于可执行文件。在 Windows 上,.exe 后缀将附加到 file 中的路径 |
shared_lib
|
用于共享库,始终写为 name 。适当的后缀和前缀由平台添加 |
python_lib
|
用于 python 库,同时替换 python 目录。适当的后缀由平台添加 |
pdb
|
用于 Windows PDB 文件。PDB 条目在非 Windows 平台上被忽略 |
implib
|
用于 Windows 导入库。这些条目在非 Windows 平台上被忽略 |
py_implib
|
用于 Windows 导入库。这些条目在非 Windows 平台上被忽略 |
implibempty
|
与 implib 相似,但库中没有导出符号 |
expr
|
file 是一个表达式。应避免使用这种类型,并在可能的情况下将其删除 |
除了 file
、python_file
和 expr
类型之外,所有路径都应不带后缀提供。
参数 | 适用于 | Description |
---|---|---|
version
|
shared_lib 、pdb |
根据平台适当设置要查找的版本 |
language
|
pdb
|
确定哪个编译器/链接器确定该文件的存在 |
shared_lib
和 pdb
类型接受一个可选的附加参数 version
,它是一个 X.Y.Z
格式的字符串,它将应用于库。每个要测试的版本都必须具有一个版本。该工具将根据平台正确应用它
python_file
、python_lib
和 py_implib
类型对使用 @<VAR>@
语法配置字符串具有基本支持
-
@PYTHON_PLATLIB@
:pythonget_install_dir
目录相对于前缀 -
@PYTHON_PURELIB@
:pythonget_install_dir(pure: true)
目录相对于前缀
pdb
接受一个可选的 language
参数。这决定了哪个编译器/链接器应该生成 pdb 文件。因为可以混合使用生成和不生成 pdb 文件的编译器(dmd 的 optlink 不生成)。目前,只有在混合使用 D 和 C 代码时才需要这样做。
{
"type": "shared_lib", "file": "usr/lib/lib",
"type": "shared_lib", "file": "usr/lib/lib", "version": "1",
"type": "shared_lib", "file": "usr/lib/lib", "version": "1.2.3.",
}
这将在每个平台上适当地应用。在 Windows 上,它需要 lib.dll
和 lib-1.dll
。在 MacOS 上,它需要 liblib.dylib
和 liblib.1.dylib
。在其他 Unix 系统上,它需要 liblib.so
、liblib.so.1
和 liblib.so.1.2.3
。
如果存在 platform
键,则只有在平台匹配时才会考虑已安装文件条目。当前支持以下 platform
值
平台 | Description |
---|---|
msvc
|
当使用类似 msvc 的编译器(msvc 、clang-cl 等)时匹配。 |
gcc
|
不是 msvc |
cygwin
|
当平台是 cygwin 时匹配。 |
!cygwin
|
不是 cygwin |
矩阵
matrix
部分可用于定义测试矩阵,以使用不同的 Meson 选项运行项目测试。
在 options
字典中,指定了所有可能的选项及其值。options
字典中的每个键都是一个 Meson 选项。它以字典格式存储所有潜在值的列表。
每个值必须包含 val
键,用于表示选项的值。null
可用于在没有当前选项的情况下添加矩阵条目。
skip_on_env
键(如下所述)可用于在值中根据当前环境跳过该矩阵条目。
expect_skip_on_jobname
和 expect_skip_on_os
键(如下所述)可用于预期测试将根据当前环境跳过。
类似地,compilers
键可用于定义编译器到语言的映射,这些语言对于此值是必需的。
{
"compilers": {
"c": "gcc",
"cpp": "gcc",
"d": "gdc"
}
}
可以使用 exclude
部分排除特定的选项组合。需要注意的是,exclude
不需要精确匹配。相反,任何包含 exclude
中所有选项值组合的矩阵条目都将被排除。因此,空字典({}
)将匹配测试矩阵中的所有元素。
上面的示例将生成以下矩阵条目
opt1=abc
opt1=abc opt2=true
opt1=abc opt2=false
opt1=qwert
opt1=qwert opt2=true
do_not_set_opts
当前支持的值是
prefix
libdir
tools
本节以简单的键值格式指定工具要求的字典。如果指定了工具,则它必须存在于环境中,并且必须满足版本要求。否则,整个测试将被跳过(包括测试矩阵中的每个元素)。
stdout
stdout
键包含一个字典列表,描述了预期的标准输出。
每个字典都包含以下键
line
-
match
(可选) -
count
(可选)
列表中的每个项目都按顺序与剩余的实际标准输出行匹配,在任何先前的匹配之后。如果实际标准输出在列表中的每个项目都匹配之前耗尽,则未看到预期的输出,并且测试失败。
字典的 match
元素决定如何匹配 line
元素
类型 | Description |
---|---|
literal
|
字面匹配(默认) |
re
|
正则表达式匹配 |
count
元素决定输出中预期出现的次数,以及允许出现的次数。如果未指定,则它必须“任意次数,但至少一次”出现。
skip_on_env
skip_on_env
键可用于指定环境变量列表。如果 skip_on_env
列表中至少存在一个环境变量,则测试将被跳过。
expect_skip_on_jobname
expect_skip_on_jobname
键包含一个字符串列表。如果设置了 MESON_CI_JOBNAME
环境变量,并且其中任何一个都是它的子字符串,则预期测试将被跳过(也就是说,预期测试将输出 MESON_SKIP_TEST
,因为 CI 环境不是它可以运行的环境,无论出于何种原因)。
如果测试意外跳过或意外运行,则测试将失败。
expect_skip_on_os
expect_skip_on_os
键可用于指定操作系统名称列表(或它们的否定,以 !
为前缀)。如果 expect_skip_on_os
列表中至少匹配一项,则预期测试将被跳过。
如果测试意外跳过或意外运行,则测试将失败。
跳过集成测试
Meson 使用多个持续集成测试系统,这些系统在指示应跳过提交方面具有略微不同的接口。
当前使用的持续集成系统
-
Azure Pipelines 允许提交消息中包含
***NO_CI***
。 - Sider 运行 Flake8 (参见下文)
为了推广一致的命名策略,请使用
-
如果要禁用所有集成测试,请在提交标题中使用
[skip ci]
文档
docs
目录包含用于生成 Meson 网站 的完整文档。在大多数情况下,行长度不应超过 70 个字符(包含链接或示例的行通常除外)。每个功能更改都必须更改文档页面。在大多数情况下,这意味着更新参考文档页面,但更大的更改可能也需要对其他文档进行更改。
所有新功能都需要在发行说明中提及。这些功能应以独立文件的形式写入 docs/markdown/snippets
目录。发布经理在发布时会将它们合并到一个页面中。
Python 代码风格
Meson 遵循基本的 Python 代码风格。附加规则如下
- 缩进 4 个空格,永远不要使用制表符
- 使用两个空格缩进 meson.build 文件
- 尽量使代码尽可能简单
- 在开始大型项目之前联系邮件列表,以避免浪费精力
Meson 使用 Flake8 来执行风格指南。项目的 Flake8 选项包含在 .flake8 中。
在 Meson 的本地克隆上运行 Flake8
$ python3 -m pip install flake8
$ cd meson
$ flake8
在提交之前自动运行它
$ flake8 --install-hook=git
$ git config --bool flake8.strict true
C/C++ 代码风格
Meson 在几种语言中有一堆测试代码。这些规则很简单。
- 缩进 4 个空格,永远不要使用制表符
- 花括号始终与 if/for/else/函数定义在同一行
外部依赖项
Meson 的目标是尽可能易于使用。用户体验应该是“获取 Python3 和 Ninja,运行”,即使在 Windows 上也是如此。不幸的是,这意味着我们不能依赖 Python 标准库以外的项目。不过,这仅适用于核心功能。对于其他帮助程序程序等,可以使用外部依赖项。如果您认为自己正在处理这种情况,请先与开发人员联系并说明您的用例。
图灵完备性
Meson 的主要设计原则是在定义语言不图灵完备。任何会导致 Meson 图灵完备的更改都会被自动拒绝。在实践中,这意味着在 meson.build
文件中定义自己的函数以及广义循环将不会添加到语言中。
我需要签署 CLA 才能贡献吗?
不需要。所有贡献都受欢迎。
无持久状态
Meson 的操作方式与函数式编程语言非常相似。它有输入,包括 meson.build
文件、选项值、编译器等等。这些输入传递给一个函数,该函数生成输出构建定义。此函数是纯函数,这意味着
- 对于任何给定的输入,输出始终相同
- 连续两次运行 Meson 始终在两次运行中产生相同的输出
后一点很重要,因为它强制执行 Meson 的连续调用之间没有“秘密状态”传递。这就是例如没有 set_option
函数,但有 get_option
函数的原因。
如果不是这样,我们就永远无法知道构建输出是否“稳定”。例如,假设有一个 set_option
函数和一个布尔变量 flipflop
。然后你可以这样做
set_option('flipflop', not get_option('flipflop'))
这段代码永远不会收敛。每次 Meson 运行都会更改选项的值,因此您从此构建定义中获得的输出将是随机的。
Meson 通过禁止这些类型的隐蔽通道来禁止这种情况。
此规则有一个例外。用户可以使用 run_command
调用外部命令。如果该命令的输出不表现得像纯函数,就会出现此问题。Meson 不会尝试防范这种情况,用户有责任确保他们运行的命令表现得像纯函数。
环境变量
环境变量就像全局变量,不同的是它们默认情况下是隐藏的。应尽可能避免使用环境变量,所有功能都应以更好的方式公开,例如命令行开关。
不适合其他地方的随机设计要点
-
所有功能都应遵循 90/9/1 规则。90% 的所有用例都应该很容易,9% 应该是可能的,如果最终的 1% 会使事情变得过于复杂,那么不支持它完全没问题。
-
任何构建目录最多都会有两个工具链:一个本地工具链和一个交叉工具链。
-
优先使用特定解决方案而不是通用框架。解决最终用户的难题,而不是为他们提供自己解决问题的工具。
-
切勿使用 Unix Shell(或 Windows Shell)的功能。不允许执行诸如使用
>
转发输出或使用&&
调用多个命令之类的操作。每当出现此类需求时,请编写一个具有所需功能的内部 Python 脚本,并使用它。
搜索结果是