2

我们正在重构我们的代码库,并试图限制不同组件之间的直接依赖关系。我们的源代码树有几个顶级目录:src/a、src/b 和 src/c。

我们要强制执行一组限制:

  • a 中的文件不能依赖于 b 或 c 中的文件
  • b 中的文件可以依赖文件 a 但不能依赖 c
  • c 中的文件可以直接依赖于 b 中的文件,但不能依赖 a

执行第一个很简单。我有一个这样的隐含规则:

build/a/%.o : src/a/%.cpp
  $(CXX) -I src/a $(OTHER_FLAGS) -o $@ $<

如果 a 下的文件尝试包含来自 b 或 c 的头文件,则构建失败,因为找不到头文件。

第二条规则有一个类似的规则,将 src/a 和 src/b 指定为包含目录。问题出现在建筑 c 上。以下是允许的。

src/c/C.cpp
#include "b.h"
void C() { ... }

src/b/b.h
#include "a.h"
class B { ... };

src/a/a.h
class A { ... };

这里,来自 c 的文件包括来自 b 的文件(允许),而后者又包括来自 a 的文件(也允许)。我们要防止这样的代码:

src/c/C_bad.cpp
// Direct inclusion of a
#include "a.h"

src/c/c_bad.h
// Direct inclusion of a
#include "a.h"

对于允许的情况进行编译,用于在 src/c 中构建文件的编译命令必须包含 -Isrc/a,但这允许第二种情况也可以编译。

我怀疑我的问题的答案是编写一个脚本,该脚本查看编译器生成的依赖项,找到潜在的非法依赖项,然后查看源文件以确定这是否是直接依赖项。有没有一种合理的方法可以结合编译器和/或 makefile 结构来做到这一点?

如果重要的话,我们正在使用 GNU Make 3.81 和 g++ 4.5.3,但如果可能的话,希望是可移植的。

更新

我们正在寻找一种需要努力违反规则的东西,而不是一种需要努力遵守规则的东西。(过去的经验表明后者不太可能奏效。)虽然在另一个答案中有一些好主意,但我接受说要编写脚本的那个,因为这是最需要努力工作的那个大约。

感谢大家的回答。

4

4 回答 4

0

是的。但这需要一些手动的努力和纪律。

在构建 C 时,您可以依赖 src/b/*.h 中的头文件。

在项目 B 中,主目录中的任何头文件都应该是自包含的,并且不依赖于其他项目。您还需要在 B src/b/detail/*.h 中创建一个子目录。在这里,头文件允许包含 src/a/*.h 和 src/b/*.h ,但这是一个私有实现细节,仅适用于 b 项目的源文件。

于 2012-12-17T17:44:30.840 回答
0

您可以使-I选项特定于目标:

build/b/%.o: CPPFLAGS += -Isrc/a
build/c/%.o: CPPFLAGS += -Isrc/b

但是,这是特定于 gnu-make 的,因此它不是可移植的。

于 2012-12-17T17:57:13.007 回答
0

最简单的方法是将所有内容的包含路径更改-Isrc为。包含语句然后具有完整的相对路径

#include <a/a.h>

例如。这使得自动检查代码变得更加容易(可能在提交挂钩而不是生成文件中)。


或者,您可以对 A 和 B 标头中的宏做一些讨厌的事情:

// src/a/a.h
#ifndef SRC_A_H
#define SRC_A_H
#ifndef ALLOW_A
#error "you're not allowed to include A headers here"
#endif
//...

// src/b/b.h
#ifndef SRC_B_H
#define SRC_B_H
#ifdef ALLOW_A_INDIRECT
#define ALLOW_A
#endif

#include <a/a.h>
//...
#ifdef ALLOW_A_INDIRECT
#undef ALLOW_A
#endif
#endif // include guard

现在这些 make 规则将允许 A 和 B 正常构建:

build/a/%.o: CPPFLAGS += -DALLOW_A
build/b/%.o: CPPFLAGS += -DALLOW_A

这将允许 C 仅通过 B 访问(以及 B 标头中的宏)

build/c/%.o: CPPFLAGS += -DALLOW_A_INDIRECT

请注意,这需要一些纪律,尤其是在 B 的标头中,但我想如果它与现有的包含守卫并列,它......好吧,它实际上仍然很讨厌。

于 2012-12-17T18:44:11.010 回答
0

考虑到您将其应用于现有代码库这一事实,我会选择“验证脚本”方法。

因此,当构建失败时,您不会修改构建过程并一次切断一个依赖项,而是会显示一个未投诉的文件列表。然后,您可以在考虑“大局”的情况下重构您的代码库,并且您所做的任何更改都将使用与以前相同的 Makefile 构建,从而简化测试和调试。

重构后,分析脚本可以继续用作合规检查器,以验证未来的更新。

这种分析的一个可能起点是使用makedependcpp -MM。例如,使用您在问题中列出的 cpp/h 文件:

[me@home]$ 查找 .
.
./b
./b/bh
。/一种
./a/啊
。/C
./c/C_bad.cpp
./c/C.cpp
./c/c_bad.h

[me@home]$ cpp -MM -Ia -Ib -Ic */*.cpp
C_bad.o: c/C_bad.cpp a/ah
Co: c/C.cpp b/bh a/ah

[me@home]$ # 这也适用于头文件
[我@home]$ cpp -Ia -Ib -Ic -MM c/c_bad.h
c_bad.o: c/c_bad.ha/ah

解析这些输出以确定每个 cpp 文件的依赖关系并标记那些不合规的文件应该是相当直接的。

这种方法的缺点是它无法区分直接和间接依赖关系,因此如果这很重要,您可能需要包括一个额外的步骤来检查源并挑选出直接依赖关系。

于 2012-12-18T11:14:34.200 回答