如何使用完全不同的构建系统处理第三方库?

时间:2012-06-01 17:04:27

标签: c++ c dependencies build-dependencies

C ++(和C,虽然它更不重要)标准状态,程序中的所有翻译单元需要具有相同的定义;这包括编译器开关之类的东西。例如,在MSVC ++上,必须在所有翻译单元中链接到正确版本的C运行时库(/MT/MD对比/MTd/MDd)。

但是,我们想要使用几个第三方依赖项,并且有几件事情:

  • 他们都使用不同的构建系统(有一个autoconf,有一个cmake,而且有一个似乎有自己的手工制作的东西..)
  • 构建系统不会在其配置中公开这些类型的交换机,并且在不同系统中对硬编码的交换机进行不同的设置。 (例如,一个图书馆强制/MD/MDd,而另一个图书馆强制/MT/MTd

我们不确定处理这类事情的最佳方法是什么。我们已经讨论了以下选项:

  • 围绕任何第三方依赖关系构建我们自己的构建系统。
    • PRO:我们知道事情会匹配
    • PRO:我们知道我们可以以正确的方式进行跨平台支持
    • CON:我们并不确切知道每个第三方构建系统是如何工作的
    • CON:很多很多工作
    • CON:如果第三方依赖关系发生变化则中断
  • 尝试使用第三方构建系统,并尝试修改它们以执行我们需要的操作。
    • PRO:似乎工作少了
    • CON:我们可能打破第三方系统
    • CON:如果第三方依赖关系发生变化则中断
    • CON:强迫我们自己的构建变得非常复杂

我们不知道该怎么办;我们无法相信我们一个人就有这些问题。我们应该选择上述选项之一,还是我没想过的第三种选择?

4 个答案:

答案 0 :(得分:6)

严格来说,您并不需要将所有库链接到同一个运行时。假设它们是DLL,如果它们通过DLL边界传递CRT数据结构,那只是一个问题。使用一个运行时在DLL中创建FILE*,并从与另一个运行时链接的DLL中使用它是一种灾难。使用一个运行时从DLL调用mallocnew,从另一个运行时调用free / delete将导致许多有趣的问题。

但只要所有与CRT相关的内容都保存在DLL内部,您就可以安全地链接到使用另一个CRT的DLL。这也意味着您的调试版本可以使用与发布CRT链接的库。同样,只要您不尝试跨库混合CRT数据结构。

另请注意,大多数编译器标志不会影响ABI,因此它们可以安全地在库(或文件)之间存在差异。改变ABI的那些通常是显而易见的,就像强行打包或堆叠对齐一样。

所以我们所做的基本上是这样的:

  • 我们尽可能地建立图书馆。在大多数情况下,这意味着我们可以通过相当简单的方法控制它应链接的运行时。这可能需要对构建系统进行非常少量的调整,但通常,只需指定是否构建调试版本或发布版本,大多数构建系统都可以选择。 (如果库使用Visual Studio作为其构建系统,我们会尽可能将其升级到2010)
  • 我们使用库自己的构建系统。其他任何东西只是苦难的一个秘诀。您希望能够相信构建系统实际上与库源代码保持同步,并且确保使用源代码附带的构建系统的最简单方法是。
  • 如果构建库是不切实际的,我们只是按原样使用它,然后我们只需要分发链接的任何运行时。我们试图避免这种情况,但是当没有其他选择可用时,它足够安全。
  • 当我们构建一个库时,我们将完全记录在我们的内部wiki上。如果我们必须升级到更新版本的库或重建它,这将是一个巨大的帮助。

我们目前依赖于三种不同的VS运行时(2005年,2008年和2010年),这很难处理,但它有效。对于其中的一个或两个,我们总是使用发布版本,即使在我们自己的代码的调试版本中也是如此。

维护起来有点混乱,但工作。我无法真正看到更好的方法。

当然,您应该最小化您拥有的第三方依赖项,并且当您选择第三方库时,其构建系统绝对应该是一个需要考虑的因素。有些人很多比其他人更痛苦。

但是最后,你可能最终不得不使用一些只是没有表现良好的构建系统的库,或者构建那些不值得付出努力,或者您无法创建库的调试版本。然后只需拿走你能得到的东西。让它使用它喜欢使用的任何运行时。

答案 1 :(得分:2)

我假设您故意不提及任何特定的库?

无论如何,您应该问自己在 构建系统中是否真的需要此第三方代码。

我们使用的第三方库被编译一次(使用各自的构建脚本)并检查正确的VC开关,然后检查DLL或LIB文件到使用lib的应用程序的源代码控制中。

因此,第三方库的编译是我们每次第三方发布时只做一次的事情,我们不会为构建系统带来构建第三方库的错综复杂的负担。

我猜这两种方法都有正确的论据,也许你可以在问题中提供一些细节,为什么你需要/想要在你的构建系统中拥有第三方库。

答案 2 :(得分:2)

你是对的 - 你并不是唯一一个遇到这类问题的人!

根据我们的经验,依赖关系的松散耦合(我认为手动复制文件的一种奇特方式)和修改第三方构建系统是最有效的 - 尤其是在构建Windows时。

我们发现一些有用的东西: -

记录您必须应用的更改(我们使用维基页面)以获取特定版本并包含此处所需的任何步骤/依赖项(例如,构建OpenSSL所需的Perl解释器)并在使用构建之前运行任何/所有包含的测试

我们发现重命名输出库以便根据ABI标记它们一直非常有用,而不是使用第三方生成的名称。

所以第三方C ++依赖关系X最终在我们的目录结构中(提交给svn)

X / [version_number] / include(使用lib的头文件)

X / [version_number] / lib / Windows(手动构建,测试并重命名的库)

例如

X-vc100.lib X-VC100-gd.lib

(我们实际上从提升名http://www.boost.org/doc/libs/1_49_0/more/getting_started/windows.html#library-naming复制了我们的命名,因为它们似乎完全合情合理)

然后您的构建系统可以选择特定的第三方依赖项(对于VS,我们使用一个继承的属性表,其中所有依赖项都被命名为用户宏,因此可以将$(x_inc)添加到项目的include目录中, $(x_lib)添加到库中 - 这些宏然后选择该特定项目所需的版本和abi。

答案 3 :(得分:1)

没有确定的答案,这取决于第三方代码接口的设计方式。如果接口是紧密耦合的(例如,共享的非不透明数据类型),最好使用自己的构建和选项重建它们。您必须分析界面并确定如何集成它们。另一方面,如果界面简单且可以轻松解耦,则可以将它们构建为dll并按需调用。当然,您将在应用程序中加载不同版本的C库,它们都有不同的io缓冲区实例,内存管理等。

如果您有可用的源代码,那么更好的选择是投入更多时间将它们集成到您自己的构建中。