吃你自己的狗粮(自用)





5.00/5 (12投票s)
源代码组织建议
引言
任何大型项目都涉及来自不同来源的代码。在重用自己的代码或从 GitHub 下载东西时,我们必须应对不同的源代码布局、包含文件和库。
这个技巧想展示一种缓解这种痛苦的方法。它是多年实践中获得的许多伤痛的结果,其中许多是自残造成的。还有一点:这不是万能药;它适用于 C/C++ 项目,并且对于使用 Visual Studio 的项目效果更好。
我们将考虑一个常见的情况,即我们开发了两个库 cool_A
和 cool_B
,它们需要在应用程序 SuperApp
中重用。
库代码布局
对于每个库,我们都有包含文件、源文件,并且我们生成一个静态链接库。这里有一些 ASCII 艺术展示了通用的代码布局
DevTreeRoot
|
+-- cool_A
| |
| +-- include
| | |
| | +-- cool_A
| | |
| | +-- hdr1.h
| | |
| | +-- hdr2.h
| +-- src
| | |
| | +-- file1.cpp
| | |
| | +-- file2.cpp
| +-- project file (cool_A.vcxproj) and other stuff
|
+-- cool_B
|
+-- include
| |
| +-- cool_B
| |
| +-- hdr1.h
| |
| +-- hdr4.h
+-- src
| |
| +-- file1.cpp
| |
| +-- file2.cpp
|
+-- project file (cool_B.vcxproj) and other stuff
到目前为止,与您已知的大部分内容没有太大区别,除了以下几点
规则 1 - 需要对用户可见的包含文件放在 include 文件夹的子文件夹中。该子文件夹与库同名。
如果 cool_A
的用户设法将 cool_A/include 放在包含路径中(稍后我们将看到如何做到),他们可以这样引用 hdr1.h 文件
#include <cool_A/hdr1.h>
优点是它避免了不同库之间的名称冲突。在我们的例子中,如果一个程序同时使用 cool_A
和 cool_B
,相应的包含指令将是
#include <cool_A/hdr1.h>
#include <cool_B/hdr1.h>
请注意,我没有提到输出库。我很快会讲到。
使用符号链接以获得乐趣和利润
Windows 用户不太习惯符号链接,因为它们在 Windows 世界中出现得相对较晚。然而,它们在管理多个项目方面可以带来显著的好处。
遵循前面所示的结构,使用 cool_A
和 cool_B
的应用程序将有一个 include
文件夹,但在该文件夹中,我们将放置指向 cool_A
和 cool_B
包含文件夹的符号链接。文件夹结构看起来会是这样的(尖括号表示符号链接)
DevTreeRoot
|
+-- SuperApp
| |
| +-- include
| | |
| | +-- <cool_A>
| | | |
| | | +-- hdr1.h
| | | |
| | | +-- hdr2.h
| | |
| | +-- <cool_B>
| | | |
| | | +-- hdr1.h
| | | |
| | | +-- hdr4.h
| | other header files
| |
| +-- src
| | |
| | +-- source files
| other files
...
要创建这些符号链接,假设您当前的目录是 SuperApp\include,您需要发出以下命令
mklink /d cool_A \DevTreeRoot\cool_A\include\cool_A
mklink /d cool_B \DevTreeRoot\cool_B\include\cool_B
有了符号链接的神奇作用,SuperApp
只需要在其包含路径中包含其 include
文件夹,所有其他使用的库将自动可用。
我们现在可以使用相同的技巧来处理静态链接库。按照惯例,我们将它们放在 lib
文件夹中。但是,这次我们将把 lib 文件夹放在开发树的根目录下,并在每个使用它的项目中放置符号链接。在不重复前面已显示的文件布局部分的情况下,这里是与 lib 文件夹相关的部分(同样,尖括号表示符号链接)
DevTreeRoot
|
+-- cool_A
| |
| ...
| +-- <lib>
| |
| all link libraries are here
+-- cool_B
| |
| ...
| +-- <lib>
| |
| all link libraries are here
+-- SuperApp
| |
| ...
| +-- <lib>
| |
| all link libraries are here
+-- lib
|
all link libraries are here
规则 2:静态链接库文件夹 lib 存在于开发树的根目录下,并通过符号链接使其对使用它的每个项目可见。
如果存在不同类型的链接库(debug、release、32 位、64 位),它们可以作为 lib 文件夹的子文件夹。
自动化
通过一个简单的批处理文件可以自动创建项目所需的符号链接。我在所有项目中都使用 mklinks.bat 这个名字,对于 SuperApp
来说,它看起来是这样的
rem Make sure DEV_TREE_ROOT environment variable is defined
:MAKELINKS
if not exist lib\nul mklink /d lib %DEV_TREE_ROOT%\lib
pushd "%~dp0include"
if not exist cool_A\nul mklink /d cool_A %DEV_TREE_ROOT%\cool_A\include\cool_A
if not exist cool_B\nul mklink /d cool_B %DEV_TREE_ROOT%\cool_B\include\cool_B
popd
与 Visual Studio 集成
这个方案可以轻松应用于任何 C/C++ Visual Studio 项目。只需对项目应用一些配置
- 将包含路径设置为(或添加到)$(SolutionDir)include
- 对于任何库,将输出路径设置为 $(SolutionDir)lib\$(PlatformTarget)\$(Configuration)\
- 对于任何应用程序(或 DLL),将库路径设置为 $(SolutionDir)lib\$(PlatformTarget)\$(Configuration)
最终注释
-
一些库可能依赖于其他库。在我们的例子中,
cool_B
可能使用cool_A
库。在这种情况下,它只需要一个指向cool_B
包含文件夹的符号链接。 -
另外两个关于在 Visual Studio 项目中放置各种文件的建议。它们已被证明能够很好地扩展,即使面对复杂的项目层次结构
- 将中间文件目录设置为 $(SolutionDir)o\$(ProjectName)\$(PlatformTarget)\$(Configuration)\
- 对于任何应用程序(或 DLL),将输出路径设置为 $(SolutionDir)app\$(ProjectName)\$(PlatformTarget)\$(Configuration)\
使用示例代码
首先,代码既不“酷”也不“超级”。它只是一些演示代码,旨在展示遵循这些规则的好处。下载它并按照以下步骤操作
- 在所有项目(
cool_A
、cool_B
、SupperApp
)中运行 mklinks.bat 脚本 - 构建库
cool_A
和cool_B
- 构建应用程序
SuperApp
结论
使用符号链接和一些简单的规则可以大大简化代码重用。Visual Studio 中使用的不同设置是多年精心调整的结果,直到我一切都弄对为止。例如,将中间文件目录设置为 $(SolutionDir)o\$(ProjectName)\$(PlatformTarget)\$(Configuration)\ 的建议基于几个目标
- 将所有对象文件放在一个地方以便于清理项目是很方便的,因此有通用的 o 文件夹。
- 您可能在一个解决方案中有多个项目,并且需要将它们的中间文件分开,因此需要 $(ProjectName) 部分。
- 当然,每个“版本”的构建(x86、x64、debug、release 等)都需要分开,因此需要 $(PlatformTarget)\$(Configuration) 部分。
这个系统对于不同的 Git 仓库效果非常好,您只需从每个仓库获取代码并使用符号链接将它们组合起来。
历史
- 2021 年 10 月 7 日 - 初始版本