如何处理具有完全不同构建系统的第三方库?
Posted
技术标签:
【中文标题】如何处理具有完全不同构建系统的第三方库?【英文标题】:How does one handle third party libraries with completely different build systems? 【发布时间】:2012-06-06 22:43:02 【问题描述】:C++(和 C,尽管在那儿不太重要)标准规定程序中的所有翻译单元都需要具有相同的定义;这包括编译器开关之类的东西。例如,在 MSVC++ 上,必须在所有翻译单元中链接到正确版本的 C 运行时库(/MT
vs /MD
vs /MTd
vs /MDd
)。
但是,我们想使用几个第三方依赖项,其中有几件事:
他们都使用不同的构建系统(有一个 autoconf,有一个 cmake,还有一个似乎有它自己的手工制作的东西..) 构建系统并非都在其配置中公开这些类型的开关,并且硬编码的开关在不同的系统中设置不同。 (例如,一个图书馆强制/MD
和 /MDd
,而另一个图书馆强制 /MT
和 /MTd
)
我们不确定处理这类事情的最佳方法是什么。我们讨论了以下选项:
围绕任何第三方依赖项构建我们自己的构建系统。 专业人士:我们知道一切都会匹配 专业人士:我们知道我们可以以正确的方式进行跨平台支持 CON:我们不知道每个第三方构建系统是如何工作的 CON:很多很多工作 CON:如果第三方依赖项发生变化则中断 尝试使用第三方构建系统,并尝试修改它们来满足我们的需要。 专业版:似乎工作量更少 CON:我们可能会破坏第三方系统 CON:如果第三方依赖项发生变化则中断 CON:迫使我们自己的构建变得非常复杂我们不知道该怎么做;我们无法相信只有我们一个人遇到这些问题。我们应该选择上述选项之一,还是我没有想到的第三种选择?
【问题讨论】:
拥有自己的构建系统对我来说有点过分。如果您的程序对 crt(MD, MT, etc) 的使用保持不变,那么编辑 conf 工具的参数应该是一种不那么耗时的方法。因为您始终可以拥有原件的副本。 这些第三方代码是以什么形式提供的?动态链接库?还是对象? @pizza:主要是来源。但通常部分构建脚本会生成配置标头和构建所需的其他类似内容。 如果这些第三方是必不可少的,如果你打算长期使用,最好自己构建构建系统。这将是很多工作,但您将对构建和维护这些第三方有更深入的了解,并且从长远来看会很有用。这就像你现在或以后再努力一样。只是我的2美分。 :) 没有依赖树很难量化。你有多少“钻石”(lib B 依赖于 lib A,lib C 依赖于 lib A,lib D 依赖于 lib B 和 lib C)?我倾向于坚持使用您的“主要”构建系统(msbuild?)并尝试创建模仿第 3 方构建系统所做的 makefile——如果它们有“非常冗长”的选项,那么做起来并不难 【参考方案1】:严格来说,您并不需要所有库都链接到同一个运行时。假设它们是 DLL,那么只有当它们通过 DLL 边界传递 CRT 数据结构时才会出现问题。使用一个运行时在 DLL 中创建 FILE*
,并从链接到另一个运行时的 DLL 中使用它是灾难的根源。使用一个运行时从 DLL 调用 malloc
或 new
,从另一个运行时调用 free
/delete
会导致很多有趣的问题。
但只要所有与 CRT 相关的内容都保存在 DLL 内部,您就可以安全地链接到使用另一个 CRT 的 DLL。这也意味着您的调试版本可以使用与发布 CRT 链接的库。同样,只要您不尝试跨库混合 CRT 数据结构。
还要注意,大多数编译器标志不会影响 ABI,因此它们可以安全地在库(或文件)之间有所不同。改变 ABI 的通常是显而易见的,例如强制打包或堆栈对齐。
所以我们所做的基本上是这样的:
只要有可能,我们都会自己构建库。在大多数情况下,这意味着我们可以通过相当简单的方式控制它应该链接到哪个运行时。这可能需要对构建系统进行少量调整,但通常只需指定是构建调试版本还是发布版本,大多数构建系统都有选择。 (如果库使用 Visual Studio 作为其构建系统,我们会尽可能将其升级到 2010) 我们使用图书馆自己的构建系统。其他任何事情都只是痛苦的秘诀。您希望能够相信构建系统实际上与库源代码保持同步,而确保这一点的最简单方法是使用源代码附带的构建系统。 如果构建库不可行,我们就按原样使用它,然后我们只需要分发它所链接的任何运行时。我们会尽量避免这种情况,但在没有其他选择的情况下这样做是足够安全的。 当我们构建一个库时,我们准确地在我们的内部 wiki 上记录它是如何完成的。如果我们必须升级到更新版本的库或重建它,这将是一个巨大的帮助。我们目前依赖于三种不同的 VS 运行时(2005、2008 和 2010),处理起来很麻烦,但它工作。对于其中一两个,我们总是使用发布版本,即使在我们自己的代码的调试版本中也是如此。
维护起来有点麻烦,但它工作。而且我真的找不到更好的方法。
当然,你应该尽量减少你有多少第三方依赖,当你选择第三方库时,它的构建系统绝对应该是一个考虑因素。有些人比其他人更痛苦。
但最终,您可能最终不得不使用一些没有良好构建系统的库,或者构建起来非常痛苦以至于不值得努力,或者在哪里您无法创建库的调试版本。然后拿走你能得到的。让它使用它喜欢使用的任何运行时。
【讨论】:
CRT 的东西不是 DLL 内部的 :((实际上一些依赖项是静态库,根本不是 DLL) 有趣的乐趣...我发现绝望和用自己的头撞墙有时是最明智的方法... 大声笑——这可能会让某人一开始感觉更好,但它对构建问题有什么作用呢? :) 好吧,我更喜欢看光明的一面。它不会使构建问题变得更糟。【参考方案2】:我假设您故意不提及任何特定的库?
无论如何,您应该问问自己,您是否真的需要在您的 构建系统中使用此第 3 方代码。
我们使用的第 3 方库被编译一次(使用它们各自的构建脚本)并检查正确的 VC 开关,然后将 DLL 或 LIB 文件检入到使用该库的应用的源代码控制中。
因此,第 3 方库的编译是我们在每个第 3 方版本中只执行一次的操作,并且我们不会因构建第 3 方库的复杂性而给构建系统带来负担。
我想这两种方法都有有效的论据,也许您可以在问题中提供一些详细信息,为什么您需要/想要在构建系统中包含第 3 方库。
【讨论】:
我完全同意。如果您不不断地对第三方库进行实时更改,则没有理由继续重建它们。如果您不断地进行实时更改,那么您还有其他问题(即,您使用的库版本不稳定,并且更改可能会绕过您通常使用的任何代码审查过程)。【参考方案3】:你是对的——你不是唯一一个遇到这些问题的人!
根据我们的经验,松散耦合的依赖项(我猜是手动复制文件的一种奇特方式)和修改第 3 方构建系统是最有效的 - 特别是在为 Windows 构建时。
我们发现一些有用的东西:-
记录您必须为特定版本应用的更改(我们使用 wiki 页面)并包括此处所需的任何步骤/依赖项(例如构建 OpenSSL 所需的 Perl 解释器)并在使用构建之前运行任何/所有包含的测试.
我们发现重命名输出库以便它们始终按照 ABI 进行标记非常有用,而不是使用第 3 方生成的名称。
所以第 3 方 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 中复制了我们的命名,因为它们看起来完全合理)
然后您的构建系统可以选择特定的第 3 方依赖项(对于 VS,我们使用继承的属性表,其中所有依赖项都命名为用户宏,因此只需将 $(x_inc) 添加到项目的包含目录中即可$(x_lib) 添加到库 - 这些宏然后选择该特定项目所需的版本和 abi。
【讨论】:
【参考方案4】:没有确定的答案,这取决于第三方代码接口是如何设计的。如果接口是紧耦合的,例如共享非透明数据类型,最好使用您自己的构建和选项重新构建它们。您必须分析接口并确定如何集成它们。另一方面,如果接口简单且易于解耦,则可以将它们构建为dll并按需调用。当然,你会在应用程序中加载不同版本的 C 库,它们都有不同的 io 缓冲区、内存管理等实例。
如果您有可用的源代码,更好的选择是投入更多时间将它们集成到您自己的构建中。
【讨论】:
以上是关于如何处理具有完全不同构建系统的第三方库?的主要内容,如果未能解决你的问题,请参考以下文章