生成源代码

有时源代码文件需要在传递给实际编译器之前进行预处理。例如,您可能想要构建一个 IDL 编译器,然后运行一些文件通过它来生成实际的源代码文件。在 Meson 中,这是通过 generator()custom_target() 完成的。

使用 custom_target()

假设您有一个构建目标,它必须使用由编译器生成的源代码进行构建。编译器可以是构建目标

mycomp = executable('mycompiler', 'compiler.c')

或者系统提供的外部程序,或者源代码树内的脚本

mycomp = find_program('mycompiler')

自定义目标可以接受零个或多个输入文件,并使用它们生成一个或多个输出文件。使用自定义目标,您可以在构建时运行此编译器以生成源代码

gen_src = custom_target('gen-output',
                        input : ['somefile1.c', 'file2.c'],
                        output : ['out.c', 'out.h'],
                        command : [mycomp, '@INPUT@',
                                   '--c-out', '@OUTPUT0@',
                                   '--h-out', '@OUTPUT1@'])

@INPUT@ 将转换为 'somefile1.c' 'file2.c'。与输出一样,您也可以通过索引分别引用每个输入文件。

然后将其放入您的程序中,您就完成了。

生成头文件

将生成的标头添加到源代码列表将确保生成标头以及为目标创建适当的包含路径

prog_python = find_program('python3')

foo_c = custom_target(
    'foo.c',
    output : 'foo.c',
    input : 'my_gen.py',
    command : [prog_python, '@INPUT@', '--code', '@OUTPUT@'],
)

foo_h = custom_target(
    'foo.h',
    output : 'foo.h',
    input : 'my_gen.py',
    command : [prog_python, '@INPUT@', '--header', '@OUTPUT@'],
)

libfoo = static_library('foo', [foo_c, foo_h])

executable('myexe', ['main.c', foo_h], link_with : libfoo)

每个依赖于生成标头的目标都应将该标头添加到其源代码中,如上所述,libfoomyexe 就是这样。这是因为 Meson 或后端无法知道 myexe 依赖于 foo.h,仅仅因为 libfoo 依赖于它,它可能是一个私有标头。

一次生成多个文件

有时,单个生成器一次创建两个或多个文件是有意义的(也许是头文件和源代码文件),Meson 也涵盖了这种情况。custom_target 可以像列表一样被索引,以分别获取每个输出文件。顺序与传递给 custom_target 的输出参数的顺序相同。

prog_python = find_program('python3')

foo_ch = custom_target(
    'foo.[ch]',
    output : ['foo.c', 'foo.h'],
    input : 'my_gen.py',
    command : [prog_python, '@INPUT@', '@OUTPUT@'],
)

libfoo = static_library('foo', [foo_ch])

executable('myexe', ['main.c', foo_ch[1]], link_with : libfoo)

在这种情况下,libfoo 依赖于 foo.cfoo.h,但 myexe 仅依赖于 foo.h,即第二个输出。

使用依赖项来管理生成的资源

在某些情况下,使用 declare_dependency 来“捆绑”标头和库依赖项可能更容易,尤其是在存在许多生成标头的情况下

idep_foo = declare_dependency(
    sources : [foo_h, bar_h],
    link_with : [libfoo],
)

有关更多信息,请参见 依赖项declare_dependency()

使用 generator()

生成器类似于自定义目标,不同之处在于我们定义了一个生成器,它定义了如何将输入文件转换为一个或多个输出文件,然后在所需数量的输入文件上使用它。

请注意,生成器仅应用于作为构建目标或自定义目标的输入使用的输出。当您在多个目标中使用生成器的处理后的输出时,生成器将被多次运行以创建每个目标的输出。每个输出都将在目标私有目录 @BUILD_DIR@ 中创建。

如果您要为一般目的生成文件,例如为多个源代码使用的标头或要安装的数据等生成文件,请使用 custom_target() 而不是。

gen = generator(mycomp,
                output  : '@[email protected]',
                arguments : ['@INPUT@', '@OUTPUT@'])

第一个参数是要运行的可执行文件。下一个文件指定一个名称生成规则。它指定如何为给定的输入名称构建输出文件名。@BASENAME@ 是输入文件名的占位符,不带前面的路径或后缀(如果有)。因此,如果输入文件名是 some/path/filename.idl,则输出名称将是 filename.c。您还可以使用 @PLAINNAME@,它保留后缀,这将导致一个名为 filename.idl.c 的文件。最后一行指定要传递给可执行文件的命令行参数。@INPUT@@OUTPUT@ 是输入和输出文件的占位符,将由 Meson 自动填充。如果您的规则生成多个输出文件并且您需要将它们传递给命令行,请将位置追加到输出占位符,如下所示:@OUTPUT0@@OUTPUT1@ 等。

使用此规则,我们可以生成源代码文件并将它们添加到目标中。

gen_src = gen.process('input1.idl', 'input2.idl')
executable('program', 'main.c', gen_src)

生成器也可以生成多个未知名称的输出文件

gen2 = generator(someprog,
                 output : ['@[email protected]', '@[email protected]'],
                 arguments : ['--out_dir=@BUILD_DIR@', '@INPUT@'])

在这种情况下,您无法使用普通的 @OUTPUT@ 变量,因为它会造成歧义。该程序只需要知道输出目录,它将自行生成文件名。

为了使每次使用时都能够传递不同的附加参数给生成器程序,您可以在 arguments 列表中使用 @EXTRA_ARGS@ 字符串。请注意,此占位符只能作为完整的字符串存在,而不能作为子字符串存在。主要原因是它代表一个字符串列表,该列表可能为空,也可能包含多个元素;无论哪种情况,将它插入单个字符串的中间都会很麻烦。如果从 process() 调用中没有传递任何额外的参数,则占位符将完全从实际的参数列表中省略,因此不会因为这个原因而将空字符串传递给生成器程序。如果 extra_args 中有多个元素,它们将作为单独的元素插入实际的参数列表中。

gen3 = generator(genprog,
                 output : '@[email protected]',
                 arguments : ['@INPUT@', '@EXTRA_ARGS@', '@OUTPUT@'])
gen3_src1 = gen3.process('input1.y')
gen3_src2 = gen3.process('input2.y', extra_args: '--foo')
gen3_src3 = gen3.process('input3.y', extra_args: ['--foo', '--bar'])

搜索结果如下