本文以Arch Linux为例介绍如何基于VS Code和Meson建立一套C言语开发环境。VS Code和Meson都是跨渠道的,其他渠道的读者能够参阅本文自行探索,大体流程都是差不多的。C 用户也能够参阅本文,只要个别参数的不同,需求变化的地方我都会标出来。
Meson介绍
依据其官方网站的介绍:
Meson 是一个开源构建系统,不仅速度极快,而且更重要的是,尽可能用户友爱。
Meson 的设计要害是,开发人员花在编写或调试构建定义上的每一刻都是糟蹋。等候构建系统实践开端编译代码的每一秒也是如此。
相同依据其官方网站的介绍,Meson有以下特性:
其间有两点很重要,一是跨渠道,与CMake看齐;二是装备文件非图灵齐备,这是用户友爱的重要原因。非图灵齐备简略来说,便是它的装备文件更像是装备文件,而不是编程言语。
Meson运用Apache 2许可证。
Meson的默许后端是Ninja,也支撑Visual Studio、Xcode后端,但不支撑Make。由于Make太慢,Makefile语法不行,支撑成本高。
Meson是用Python写的,装备文件的语法也基于Python。用Python编写算是Meson的一个下风,意味着依靠比较庞大。目前有一些其他言语的实现,但还没达到普遍可用的状况。
目前运用Meson的主要是Gnome、Systemd等红帽系项目,但PostgreSQL、QEMU、mpv等多个无关项目也在运用,说明其才能仍是被广泛认可的。
环境装置
首要需求装置VS Code与Meson,在Arch Linux上,能够经过以下指令完结:
yay -Sy visual-studio-code-bin meson
假如没有yay,能够前往AUR手动装置VS Code,参阅Linux下VS Code装置与C编程环境装备
。Meson能够直接经过pacman
装置。
假如还没有编译器和调试器,在Arch Linux上,能够经过以下指令装置:
sudo pacman -Sy gcc gdb
之后装置必需的VS Code扩展:
- C/C : ms-vscode.cpptools
- Meson: mesonbuild.mesonbuild
Meson的VS Code扩展是官方推出的,运用体会很差,但没有更好的替代品。要运用扩展的格式化功用,还必需装置另一个程序muon。
Meson扩展的内嵌提示主张关掉,没有价值,还影响阅览。
第一个项目
VS Code以目录为作业区,先创立一个目录,以demo为例,然后用VS Code翻开这个目录。
再创立源文件,以main.c为例,随意写点什么,比方闻名的”hello, world”。
#include <stdio.h>
int main(void) {
puts("hello, world");
return 0;
}
现在咱们现已有了齐备的源代码,能够构建出一个可履行程序。接下来编写构建装备文件。
首要创立一个名为meson.build的文件,这是Meson的装备文件,而且文件名是特定的。
这时Meson扩展会弹出一条提示,询问你是否装备Meson项目,其实便是是否履行meson setup
指令。这时先不必管,由于咱们的装备文件还没写完,肯定会失利。
和Python相同,Meson的行注释也是以#
最初。除注释以外,Meson装备文件有必要以project()
最初,标明项目的姓名、言语、版别、默许选项等。其间姓名和言语是必需的,依次是函数的前两个方位参数。
# Meson configuration for demo
project('demo', 'c',
version: '0.0.1',
default_options: {
'c_std': 'c17',
'warning_level': '3',
'werror': true,
'optimization': 'g',
'strip': true,
},
)
假如项目存在多个言语,能够一起指定,比方project('demo', 'c', 'cpp')
。
Meson的值是有类型的,基本类型有字符串、数字、bool、列表、字典。字符串以单引号包裹,不支撑双引号。
Meson大部分的可变参数都是扁平化的,能够把任意嵌套的列表打开为接连的参数。project('demo', 'c', 'cpp')
和project('demo', ['c', 'cpp'])
是等价的,为了提高可读性,本文都会运用后一种写法。
default_options
是对所有构建目录都收效的默许选项,之后还能够针对特定的构建目录履行meson configure
指令来改动这些选项。
这些选项有的会增加编译器参数,有的会增加链接参数,有的会改动装置时的行为。
-
c_std
选择C言语的规范版别,比方c17
就为编译器增加-std=c17
参数。假如编程言语为C ,选项名为cpp_std
。假如没有指定这个选项,则言语版别是由编译器决议的。 -
warning_level
指定正告等级,不同等级对应的编译器参数可对照下表。由于有everything
这么个值的存在,所以选项的类型是字符串。需求留意的是,gcc并没有-Weverything
这么个参数,所以不要运用everything
这个正告等级。正告等级3
包含了-Wpedantic
,这会对不符合言语规范的写法发出正告,假如设置了c_std
,warning_level
主张设置为3
。默许是1
。
Warning level | GCC/Clang | MSVC |
---|---|---|
0 | ||
1 | -Wall | /W2 |
2 | -Wall -Wextra | /W3 |
3 | -Wall -Wextra -Wpedantic | /W4 |
everything | -Weverything | /Wall |
-
werror
为编译器增加-Werror
参数。这会让所有的正告变成过错,然后让编译失利。我强烈主张敞开这个选项,让开发者不再疏忽正告,然后消除隐患。假如是故意为之,也能够显式消除正告,提高代码可读性。默许是false
。 -
optimization
改动编译器的优化等级,g
代表参数-Og
。Meson的默许构建类型是debug
,等价于参数-g -O0
,-Og
能够提高调试体会。假如meson setup
或meson configure
指令指定buildtype
为release
,等价于-O3
,optimization
会被覆盖掉,所以不必忧虑optimization
选项对meson setup
或meson configure
指令造成影响。 -
strip
选项表示在履行meson install
指令时对二进制文件进行strip,这会减少二进制文件的体积。默许是false
.
接着咱们指定此次构建的方针,以及它依靠哪些源文件。
executable('demo', sources: 'main.c', install: true)
executable
的第一个方位参数标明晰可履行文件的姓名,要害字参数sources
标明晰编译哪些源文件,install
标明晰在履行meson install
指令时是否装置这个可履行文件。
最终的meson.build文件长这样:
# Meson configuration for demo
project('demo', 'c',
version: '0.0.1',
default_options: {
'c_std': 'c17',
'warning_level': '3',
'werror': true,
'optimization': 'g',
'strip': true,
},
)
executable('demo', sources: 'main.c', install: true)
此刻就能够完结构建了,点击扩展提示的Yes,会创立一个名为builddir的目录。
假如这个提示消失了,也能够在项目根目录下手动运转以下指令生成构建目录。
meosn setup builddir
目录名不能改,由于这是Meson扩展的默许目录名,后续很多操作都依靠于各个姓名。
这时还会弹出一个提示,问你是否下载言语服务器,这个有必要下载,否则扩展功用少一半。
选Yes后,会在你的全局设置里加一条"mesonbuild.downloadLanguageServer": true
。
生成构建目录的一起也会生成一系列装备文件,比方build.ninja便是Ninja的装备文件,.gitignore 和 .hgignore别离是Git和Mercurial版别控制系统的疏忽文件,builddir下的所有内容都不会增加到版别控制系统中。
除此之外,Meson扩展还能与C/C 扩展集成,经过compile_commands.json文件装备C/C 扩展的代码提示,体现为主动生成作业区装备。
这时咱们履行meson compile -C builddir
指令,或许运用VS Code指令Meson: Build
就能够在builddir下生成一个名为demo的可履行文件。
调试
Meson扩展供给了一系列任务,但没有供给调试装备。
假如咱们修正Meson: Build all targets
,能够看到里边的内容是这样的。
{
"version": "2.0.0",
"tasks": [
{
"type": "meson",
"mode": "build",
"problemMatcher": [
"$meson-gcc"
],
"group": "build",
"label": "Meson: Build all targets"
}
]
}
履行这个任务,在终端能够看到实践履行的指令。
接下来手写用于调试的launch.json文件,对这个文件不了解的读者能够参阅Linux下VS Code装置与C编程环境装备 。
{
"version": "0.2.0",
"configurations": [
{
"name": "debug",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/${config:mesonbuild.buildFolder}/demo",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}/rundir",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": false
}
],
"preLaunchTask": "Meson: Build all targets"
}
]
}
这儿我把cwd
设置为一个独自的目录,防止打乱代码树。preLaunchTask
能够在每次调试前都履行一次构建。
现在万事俱备,能够按F5
开端调试了。
多文件项目
当然咱们不会为了一个源文件而运用构建东西,现在咱们提高项目复杂度。创立两个子目录a 和 b,每个子目录下都有对应的源文件和头文件。让main.c 依靠 a.h,让a.c 依靠 b.h。
// main.c
#include <stdio.h>
#include "a/a.h"
int main(void) {
puts("hello, world");
fn_a();
return 0;
}
// a.h
extern void fn_a(void);
// a.c
#include <stdio.h>
#include "b/b.h"
void fn_a(void) {
puts("fn_a");
fn_b();
}
// b.h
extern void fn_b(void);
// b.c
#include <stdio.h>
void fn_b(void) {
puts("fn_b");
}
文件准备就绪,现在咱们需求对meson.build进行一点修正。
# Meson configuration for demo
project('demo', 'c',
version: '0.0.1',
default_options: {
'c_std': 'c17',
'warning_level': '3',
'werror': true,
'optimization': 'g',
'strip': true,
},
)
sources = [
'main.c',
'a/a.c',
'b/b.c',
]
executable('demo', sources: sources, install: true)
改动点只要把本来的'main.c'
扩大成3个源文件,为了提高可读性,咱们还把源文件列表独立成一个变量。
观察上面的几个文件,有两个要害信息:
- meson.build里只要源文件,没有头文件,也没有标明任何头文件依靠。这是由于Ninja能够凭借编译器发生的信息主动剖析头文件依靠,而且在必要的时分从头编译源文件。
- 源文件的
#include
指令并不是相对于自身的途径,而是相对于项目根目录的。这是由于meson.build里的executable()
函数会主动增加包含途径,一个是当时meson.build地点的目录,另一个是builddir中对应可履行文件地点的目录。
修正meson.build后,不需求从头履行meson setup
,能够直接进行构建,build.ninja里有钩子,主动读取meson.build而且更新builddir里的装备文件。
修正源文件或头文件的内容(留意不是增加源文件)后,也不需求从头履行meson setup
,构建时Ninja会主动剖析依靠并从头编译需求的文件。
履行结果:
hello, world
fn_a
fn_b
依靠办理
假如咱们的项目依靠第三方供给的库,需求加一些编译参数或链接参数。比方libgcrypt,熟悉gcc的朋友应该知道要加-lgcrypt
参数。不过直接加参数既不好办理,又不行移植。Meson有内置的依靠办理,能够凭借pkg-config
查找依靠并主动增加编译和链接参数。
能够运用add_project_dependencies()
函数为整个项目的所有构建方针都增加依靠,也能够经过构建方针的dependencies
参数独自增加依靠。
# Meson configuration for demo
project('demo', 'c',
version: '0.0.1',
default_options: {
'c_std': 'c17',
'warning_level': '3',
'werror': true,
'optimization': 'g',
'strip': true,
},
)
libgcrypt = dependency('libgcrypt')
# add_project_dependencies(libgcrypt, language: 'c')
executable('demo', sources: 'main.c', dependencies: libgcrypt, install: true)
然后编写咱们的源文件。
// main.c
#include <stdio.h>
#include <string.h>
#include <gcrypt.h>
int main(void) {
puts("hello, world");
enum gcry_md_algos algo = GCRY_MD_MD5;
unsigned int digest_len = gcry_md_get_algo_dlen(algo);
char source[] = "password";
void *result = malloc(digest_len);
gcry_md_hash_buffer(algo, result, source, strlen(source));
for (unsigned int iter = 0; iter < digest_len; iter ) {
printf("x", ((unsigned char*)result)[iter]);
}
putchar('n');
return 0;
}
构建并运转:
hello, world
5f4dcc3b5aa765d61d8327deb882cf99
能够在终端的输出里看到查找依靠的进程。
The Meson build system
Version: 1.3.0
Source dir: /home/lonble/demo
Build dir: /home/lonble/demo/builddir
Build type: native build
Project name: demo
Project version: 0.0.1
C compiler for the host machine: cc (gcc 13.2.1 "cc (GCC) 13.2.1 20230801")
C linker for the host machine: cc ld.bfd 2.41.0
Host machine cpu family: x86_64
Host machine cpu: x86_64
Found pkg-config: YES (/usr/bin/pkg-config) 2.1.0
Run-time dependency libgcrypt found: YES 1.10.3-unknown
Build targets in project: 1
咱们还能够指定依靠版别,以及是否运用静态库。
libgcrypt = dependency('libgcrypt', version: '>=1.5', static: false)
Meson还对线程库进行了封装。
threads = dependency('threads')
假如一些库既没有pkg-config
文件,也没有Meson封装,还能够经过编译器手动查找。这些库一般都是glibc从C规范库里拆分出来的,以数学库libm
为例:
cc = meson.get_compiler('c')
math = cc.find_library('m', required: false)
find_library()
的返回值类型和dependency()
相同,所以返回值的用法也是相同的。由于除Linux以外的其他渠道都没有独自拆分的数学库,所以设置了required: false
,假如没找到这个库也不会报错。
自定义编译和链接参数
在project()
函数的default_options
参数里,就有名为c_args
和c_link_args
的键,对应的C 版别为cpp_args
和cpp_link_args
。这两个键别离设置编译和链接参数。
project('demo', 'c',
default_options: {
'c_args': ['-ansi', '-Wmain'],
'c_link_args': ['-s', '-static'],
},
)
我强烈不主张在default_options
中增加编译和链接参数,原因如下:
-
default_options
在修正后不会主动同步到builddir,假如要强行覆盖,有必要履行meson setup --wipe builddir
,这会清空现已生成的方针文件 - 在这儿设置编译和链接参数会覆盖
CFLAGS
环境变量
为整个项目增加编译参数的推荐办法是add_project_arguments()
函数。
add_project_arguments(['-ansi', '-Wmain'], language: 'c')
这儿的language
参数不能省掉。
相同能够运用add_project_link_arguments()
函数为整个项目增加链接参数,用法和add_project_arguments()
是相同的。
还能够针对每个构建方针独自设置参数。
executable('demo',
sources: 'main.c',
install: true,
c_args: ['-ansi', '-Wmain'],
cpp_args: ['-Weffc ', '-Wnamespaces'],
link_args: ['-s', '-static']
)
需求留意,自定义参数不利于程序的可移植性,由于这些参数都是特定于编译器或渠道的。咱们能够针对不同渠道运用不同的参数,尽可能确保程序的可移植性。
args = []
arg_syntax = meson.get_compiler('c').get_argument_syntax()
if arg_syntax == 'gcc'
args = ['-Wall', '-Wextra']
elif arg_syntax == 'msvc'
args = '/W3'
endif
add_project_arguments(args, language: 'c')
装置产品
Meson对产品装置也有一套规范流程。首要只要标记install: true
的构建方针才会被装置,构建方针有多个品种,每个品种都有对应的装置目录。
这是从官网扒下来的默许目录表。
Option | Default value | Description |
---|---|---|
prefix | see below | Installation prefix |
bindir | bin | Executable directory |
datadir | share | Data file directory |
includedir | include | Header file directory |
infodir | share/info | Info page directory |
libdir | see below | Library directory |
licensedir | Licenses directory | |
libexecdir | libexec | Library executable directory |
localedir | share/locale | Locale data directory |
localstatedir | var | Localstate data directory |
mandir | share/man | Manual page directory |
sbindir | sbin | System executable directory |
sharedstatedir | com | Architecture-independent data directory |
sysconfdir | etc | Sysconf data directory |
-
prefix
在Windows上是C:/
,其他渠道是/usr/local
-
libdir
是依据渠道主动估测的,不同发行版也有区别
当prefix
设置为某些特定值时,其他选项的默许值会改动。
- 当
prefix
为/usr
时,sysconfdir
默许为/etc
,localstatedir
默许为/var
,sharedstatedir
默许为/var/lib
- 当
prefix
为/usr/local
时,localstatedir
默许为/var/local
,sharedstatedir
默许为/var/local/lib
假如装置目录是相对途径,则是相对于prefix
的;假如是绝对途径,则疏忽prefix
。
这些选项的默许值能够经过project()
函数的default_options
参数改动,也能够履行meson configure
指令手动修正。
每个构建方针也能够经过install_dir
参数改动自己的装置目录。
executable('demo',
sources: 'main.c',
install: true,
install_dir: 'exec'
)
以咱们最简略的”hello, world”程序为例。此刻咱们没有改动任何装置目录,所以可履行文件demo的装置目录是/usr/local/bin
。假如装置时缺少权限,Meson会向你索要权限。
# Meson configuration for demo
project('demo', 'c',
version: '0.0.1',
default_options: {
'c_std': 'c17',
'warning_level': '3',
'werror': true,
'optimization': 'g',
'strip': true,
},
)
executable('demo', sources: 'main.c', install: true)
Meson默许的buildtype
是debug
,咱们当然不能装置debug版别。履行下面的指令生成release构建目录,默许敞开-O3
优化。一起修正装置目录。接着就能够履行meson install
指令装置,装置前会检查是否需求构建,确保产品最新。
meson setup --buildtype=release --prefix="$HOME/.local" build_release
meson install -C build_release
从终端输出能够看到demo被装置到了$HOME/.local/bin
目录下,而且履行了strip
。
ninja: Entering directory `/home/lonble/demo/build_release'
[2/2] Linking target demo
Installing demo to /home/lonble/.local/bin
Stripping target 'demo'