我有一个同时包含 c 和 cpp 文件的项目,我一直在使用 NMake 进行构建。我的问题是,如果我有两个推理规则,每种文件类型一个,
{$(dirSrc)}.c{$(dirObj)}.obj:
cl /nologo /c /EHsc /Fo$(dirObj) $<
{$(dirSrc)}.cpp{$(dirObj)}.obj:
cl /nologo /c /EHsc /Fo$(dirObj) $<
$(binPath): $(dirObj)*.obj
link /nologo /dll /out:$(binPath) $(dirObj)*.obj
只有 C 文件被编译,大概是因为 .c 扩展名在 .后缀列表。
我当然可以简单地将 c 文件的扩展名更改为 cpp,但我想知道是否有人知道调用这两个规则的方法。
(作为同舟其他人的参考)问题是*.obj
通配符。
再加上如果一个目标有多个(推理)规则,则只能合理地应用一个规则来创建/更新它。
现在,干净状态的生成逻辑如下(简化):
-
需要构建
$(binPath)
,让我们看看它依赖于什么... -
有那个
*.obj
,所以让我们不要仔细看看... -
不匹配任何现有文件;检查我们是否可以以某种方式构建它...
-
很酷,推理规则说
.obj
可以从.c
(或.cpp
构建,真的没关系,先找到哪一个,见下文!顺便说一句,这里重要的不是.SUFFIXES
顺序,而是 makefile 中规则的顺序)...... -
因此,在规则中执行编译器以从其匹配源(即不可扩展的文字
*.c
)创建*.obj
:→
cl /c /Fo($outdir) *.c
-
太好了,
*.obj
现在已经准备好了,继续链接... -
好的,最终目标,并且(即使链接现在对于未定义的外部可能失败)这就是我们所能做的,所以让我们称之为一天。
请注意,替代规则甚至从未出现过!
现在,对于更新,假设损坏的(不完整的)目标不存在,但上面构建的对象文件存在,甚至有一些更新,包括一些.c
和.cpp
文件:
需要构建缺失的
$(binPath)
,让我们看看它取决于什么...还有
*.obj
,再次,现在可以匹配现有文件,所以让我们检查它们(一个接一个)是否需要更新......然后,再次出现相同的推理规则(无论哪种;一个项目只能有一个,让我们坚持使用
.c
)与相关目标.obj
匹配,因此请检查相应.c
源中的更改......它们被发现了,所以"运行编译器",但这次 NMAKE 足够聪明,只能通过
$<
宏提供更新的 C 源:→
cl /c /Fo($outdir) some.c other.c
(注意:它甚至足够聪明(显然,这里有VS 2019)可以继续默认使用批处理模式,即使它不是明确的批处理规则。
好的,
*.obj
现在再次更新,继续链接...最终目标,和以前一样,工作结束,庆祝!(现在让我们假设链接已经成功,只是为了下面的其余示例。
再次:另一个规则从不需要/用于任何事情。
现在,奇怪的是,您甚至可以开始逐个删除.obj文件(只要保留一些文件),并且不担心NMAKE:
'test.dll' is up-to-date
什么?!为什么不重建缺失的?
你知道吗?让我们删除所有这些...而且,只是为了好玩,通过在那里(从任何地方)复制一些随机文件,将其重命名为fake.obj
来用一个假文件替换它们。
'test.dll' is up-to-date
耶稣!这毫无意义!
好吧,让我们结束这个。然后创建一个全新的.c
文件。这肯定会触发重建!
'test.dll' is up-to-date
没办法! :-o这到底是怎么回事?!也许添加一个新.cpp
然后...
'test.dll' is up-to-date
天哪...这样的垃圾!不可思议,这个NMAKE的东西...右?
井。。。首先,对于fake.obj
,没有具有匹配名称的源,这两个规则都不适用:NMAKE 不能为此"发明"源,所以它永远不会被重建,它只是坐在那里,作为一个定时炸弹,直到下一轮链接,链接器最终会拿起它并找出它,结束所有的乐趣。:)
对于所有其他"异常":
任何现有的.obj 文件都将满足
*.obj
的依赖关系(对于库),所以只要至少有一个,NMAKE 就会很高兴,甚至永远不知道它不是完整的列表!这就是为什么没有为任何新
.c
或添加到项目中.cpp
做任何事情,所以以这种方式使用通配符是在搬起石头砸自己的脚。(这并不意味着构建脚本中没有任何完全合法的通配符情况,顺便说一句。而且(回顾一下),为了重建丢失的对象,NMAKE(就像gmake等)必须选择一个赢家,如果有多个匹配规则,则忽略其余的。
(仅供参考,gmake甚至有一个专门关于这个通配符陷阱的页面。而且,为了降低风险,与NMAKE不同,它似乎拒绝为*.o
通配符构建初始的"完整"对象集,因为我们知道当我们开始添加源的那一刻,它将变得不完整 - 即通配符希望支持的行为!;) )
好吧,要回答我自己的问题,我能想到的最好的方法是编译到 2 个单独的目录,然后在运行链接器时指向这两个目录。
{$(dirSrc)}.c{$(dirObj)c}.obj:
cl /nologo /c /EHsc /Fo$(dirObj)c $<
{$(dirSrc)}.cpp{$(dirObj)cpp}.obj:
cl /nologo /c /EHsc /Fo$(dirObj)cpp $<
$(binPath): $(dirObj)c*.obj $(dirObj)cpp*.obj
link /nologo /dll /out:$(binPath) $(dirObj)c*.obj $(dirObj)cpp*.obj