makefile是否可以在C ++中强制执行依赖性限制

时间:2012-12-17 17:36:53

标签: c++ makefile

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

我们希望强制执行一系列的撤销:

  • a中的文件不能依赖b或c中的文件
  • b中的文件可能取决于文件a而不是c
  • c中的文件可以直接依赖于b中的文件而不是

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

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

如果某个文件试图包含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(允许)的文件,该文件又包括来自(也是允许的)的文件。我们想要阻止这样的代码:

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 个答案:

答案 0 :(得分:0)

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

构建C时,您可以依赖src / b / * .h。

中的标题

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

答案 1 :(得分:0)

您可以将-I选项设为特定目标:

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

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

答案 2 :(得分:0)

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

#include <a/a.h>
例如,

。这使得更容易自动检查代码(可能在提交挂钩而不是makefile中)。


或者,你可以在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

现在这些制作规则将允许A和B构建好:

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

这样只允许通过B(以及B&#39标头中的宏)进行C访问

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

请注意,这需要一些纪律,特别是在B&B的标题中,但我想如果它与现有的包含警卫并列,那么......好吧,它实际上仍然非常讨厌。

答案 3 :(得分:0)

考虑到您在现有代码库中应用此功能,我会选择“验证脚本”方法。

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

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

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

[me@home]$ find .
.
./b
./b/b.h
./a
./a/a.h
./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/a.h
C.o: c/C.cpp b/b.h a/a.h

[me@home]$ # This also works for header files
[me@home]$ cpp -Ia -Ib -Ic -MM c/c_bad.h 
c_bad.o: c/c_bad.h a/a.h

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

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