本文以Arch Linux为例介绍如何基于VS Code和Meson建立一套C言语开发环境。VS Code和Meson都是跨渠道的,其他渠道的读者能够参阅本文自行探索,大体流程都是差不多的。C 用户也能够参阅本文,只要个别参数的不同,需求变化的地方我都会标出来。

Meson介绍

依据其官方网站的介绍:

Meson 是一个开源构建系统,不仅速度极快,而且更重要的是,尽可能用户友爱。

Meson 的设计要害是,开发人员花在编写或调试构建定义上的每一刻都是糟蹋。等候构建系统实践开端编译代码的每一秒也是如此。

相同依据其官方网站的介绍,Meson有以下特性:

  • 对 Linux、macOS、Windows、GCC、Clang、Visual Studio 等的多渠道支撑
  • 支撑的言语包括 C、C 、D、Fortran、Java、Rust
  • 以非常可读且用户友爱的非图灵齐备 DSL 构建定义
  • 对许多操作系统以及裸机的交叉编译支撑
  • 针对完整和增量构建进行了优化,速度极快而不牺牲正确性
  • 内置的多渠道依靠供给程序,可与发行版包一同运用
  • 趣味!

其间有两点很重要,一是跨渠道,与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扩展:

Meson的VS Code扩展是官方推出的,运用体会很差,但没有更好的替代品。要运用扩展的格式化功用,还必需装置另一个程序muon

Meson扩展的内嵌提示主张关掉,没有价值,还影响阅览。

VS Code + Meson建立C开发环境

第一个项目

VS Code以目录为作业区,先创立一个目录,以demo为例,然后用VS Code翻开这个目录。

再创立源文件,以main.c为例,随意写点什么,比方闻名的”hello, world”。

#include <stdio.h>
int main(void) {
    puts("hello, world");
    return 0;
}

现在咱们现已有了齐备的源代码,能够构建出一个可履行程序。接下来编写构建装备文件。

首要创立一个名为meson.build的文件,这是Meson的装备文件,而且文件名是特定的。

VS Code + Meson建立C开发环境

这时Meson扩展会弹出一条提示,询问你是否装备Meson项目,其实便是是否履行meson setup指令。这时先不必管,由于咱们的装备文件还没写完,肯定会失利。

VS Code + Meson建立C开发环境

和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_stdwarning_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 setupmeson configure指令指定buildtyperelease,等价于-O3optimization会被覆盖掉,所以不必忧虑optimization选项对meson setupmeson 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的目录。

VS Code + Meson建立C开发环境

假如这个提示消失了,也能够在项目根目录下手动运转以下指令生成构建目录。

meosn setup builddir

目录名不能改,由于这是Meson扩展的默许目录名,后续很多操作都依靠于各个姓名。

VS Code + Meson建立C开发环境

这时还会弹出一个提示,问你是否下载言语服务器,这个有必要下载,否则扩展功用少一半。

VS Code + Meson建立C开发环境

Yes后,会在你的全局设置里加一条"mesonbuild.downloadLanguageServer": true

生成构建目录的一起也会生成一系列装备文件,比方build.ninja便是Ninja的装备文件,.gitignore.hgignore别离是GitMercurial版别控制系统的疏忽文件,builddir下的所有内容都不会增加到版别控制系统中。

VS Code + Meson建立C开发环境

除此之外,Meson扩展还能与C/C 扩展集成,经过compile_commands.json文件装备C/C 扩展的代码提示,体现为主动生成作业区装备。

VS Code + Meson建立C开发环境

这时咱们履行meson compile -C builddir指令,或许运用VS Code指令Meson: Build就能够在builddir下生成一个名为demo的可履行文件。

VS Code + Meson建立C开发环境

VS Code + Meson建立C开发环境

调试

Meson扩展供给了一系列任务,但没有供给调试装备。

VS Code + Meson建立C开发环境

假如咱们修正Meson: Build all targets,能够看到里边的内容是这样的。

{
    "version": "2.0.0",
    "tasks": [
        {
            "type": "meson",
            "mode": "build",
            "problemMatcher": [
                "$meson-gcc"
            ],
            "group": "build",
            "label": "Meson: Build all targets"
        }
    ]
}

履行这个任务,在终端能够看到实践履行的指令。

VS Code + Meson建立C开发环境

接下来手写用于调试的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开端调试了。

多文件项目

当然咱们不会为了一个源文件而运用构建东西,现在咱们提高项目复杂度。创立两个子目录ab,每个子目录下都有对应的源文件和头文件。让main.c 依靠 a.h,让a.c 依靠 b.h

VS Code + Meson建立C开发环境

// 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个源文件,为了提高可读性,咱们还把源文件列表独立成一个变量。

观察上面的几个文件,有两个要害信息:

  1. meson.build里只要源文件,没有头文件,也没有标明任何头文件依靠。这是由于Ninja能够凭借编译器发生的信息主动剖析头文件依靠,而且在必要的时分从头编译源文件。
  2. 源文件的#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_argsc_link_args的键,对应的C 版别为cpp_argscpp_link_args。这两个键别离设置编译和链接参数。

project('demo', 'c',
    default_options: {
        'c_args': ['-ansi', '-Wmain'],
        'c_link_args': ['-s', '-static'],
    },
)

强烈不主张default_options中增加编译和链接参数,原因如下:

  1. default_options在修正后不会主动同步到builddir,假如要强行覆盖,有必要履行meson setup --wipe builddir,这会清空现已生成的方针文件
  2. 在这儿设置编译和链接参数会覆盖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默许为/etclocalstatedir默许为/varsharedstatedir默许为/var/lib
  • prefix/usr/local时,localstatedir默许为/var/localsharedstatedir默许为/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默许的buildtypedebug,咱们当然不能装置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'