包装最佳实践和技巧

在为项目编写 Meson 构建定义时,您需要考虑几个因素。 尤其当项目用作子项目时更是如此。 本页列出了编写定义时要考虑的一些事项。

不要将 config.h 放入外部搜索路径

许多项目使用一个 config.h 头文件来在内部配置项目。 这些文件永远不会安装到系统头文件,因此不会发生包含冲突。 子项目则不然,您的项目树可能包含任意数量的配置文件,因此我们需要确保它们不会冲突。

基本问题是子项目的使用者必须能够包含子项目头文件,而不会看到其 config.h 文件。 最正确的解决方案是将 config.h 文件重命名为唯一名称,例如 foobar-config.h。 除非您是所述子项目的维护者,否则这通常不可行。

务实的解决方案是将配置头文件放在没有任何其他头文件的目录中,然后将其隐藏在所有其他用户之外。 一种方法是创建一个名为 internal 的顶级子目录,并使用它来构建您自己的源文件,如下所示

subdir('internal') # create config.h in this subdir
internal_inc = include_directories('internal')
shared_library('foo', 'foo.c', include_directories : internal_inc)

许多项目将他们的 config.h 保留在没有任何其他源文件的顶级目录中。 在这种情况下,您无需移动它,而是可以执行以下操作

internal_inc = include_directories('.') # At top level meson.build

使库能够以静态和共享方式构建

某些平台(例如 iOS)要求静态链接主应用程序中的所有内容。 在其他情况下,您可能需要共享库。 它们在开发过程中也更快,因为 Meson 的重新链接优化。 然而,在所有构建上构建两种库类型速度慢且浪费资源。

您的项目应使用 library 方法,该方法可以使用 default_library 内置选项在共享和静态之间切换。

mylib = library('foo', 'foo.c')

显式声明生成的标头

Meson 的 Ninja 后端的工作方式不同于 Make 和其他系统。 它不是逐个目录处理事物,而是查看整个构建定义,并按看似随机的顺序运行各个编译作业。

这样做的原因是这效率更高,因此您的构建完成得更快。 缺点是您必须小心处理依赖项。 这里最常见的问题是编译时生成的标头(例如,使用代码生成器)。 如果在构建使用这些库的代码时需要这些标头,则编译作业可能会在代码生成步骤之前运行。 解决方法是像这样明确声明依赖项

myheader = custom_target(...)
mylibrary = shared_library(...)
mydep = declare_dependency(link_with : mylibrary,
  include_directories : include_directories(...),
  sources : myheader)

然后,您可以像往常一样使用依赖项

executable('dep_using_exe', 'main.c',
  dependencies : mydep)

Meson 会确保在编译 main.c 之前已构建头文件。

避免在 declare_dependency 中公开可编译的源文件

declare_dependency 中的 sources 参数的主要用途是为后端构造正确的依赖关系图,如上一节所述。 务必注意,它不应用于直接公开依赖项的可编译源文件(.c.cpp 等),而应仅用于头文件/配置文件。 以下示例将说明如果不小心公开可编译的源文件可能会出现什么问题。

因此,您已经了解了统一构建以及 Meson 如何对其提供原生支持。 您决定公开依赖项的源文件,以实现包含其依赖项的统一构建。 对于您的支持库,您执行以下操作

my_support_sources = files(...)

mysupportlib = shared_library(
  ...
  sources : my_support_sources,
  ...)
mysupportlib_dep = declare_dependency(
  ...
  link_with : mylibrary,
  sources : my_support_sources,
  ...)

对于您的主项目,您执行以下操作

mylibrary = shared_library(
  ...
  dependencies : mysupportlib_dep,
  ...)
myexe = executable(
  ...
  link_with : mylibrary,
  dependencies : mysupportlib_dep,
  ...)

这极其危险。 在构建时,mylibrary 将构建并将支持源文件 my_support_sources 链接到生成的共享库中。 然后,对于 myexe,这些相同的支持源文件将再次编译,并链接到生成的执行文件中,除了它们已经存在于 mylibrary 中之外。 这可能会很快违反 C++ 中的单定义规则 (ODR),因为您对一个符号的定义不止一个,从而产生未定义的行为。 虽然 C 没有严格的 ODR 规则,但标准中没有语言保证此类行为有效。 违反 ODR 可能导致奇怪的特异性错误,例如段错误。 在绝大多数情况下,通过 declare_dependency 中的 sources 参数公开库源文件因此是不正确的。 如果您希望获得全面的跨库性能,请考虑将 mysupportlib 构建为静态库,并使用 LTO。

此规则有一些例外。 如果对库的使用方式存在一些自然约束,则可以公开源文件。 例如,用于 GoogleTest 的 WrapDB 模块直接公开 GTest 和 GMock 的源文件。 这是有效的,因为 GTest 和 GMock 将永远只用于终端链接目标。 终端目标是依赖项链接链中的最终目标,例如最后一个示例中的 myexe,而 mylibrary 是一个中间链接目标。 对于大多数库来说,此规则并不适用,因为您通常无法控制其他人如何使用您的库,因此不应公开源文件。

搜索结果为