为什么gnumake重新制作中间文件?

时间:2018-11-27 00:46:30

标签: gnu-make

我有一个非常简单的makefile,如下所示:

.PHONY: clean all

CC              = /home/utils/gcc-5.2.0/bin/g++
CFLAGS          = -Wall -Werror -fPIC 

SRC             = $(wildcard *.c)
OBJ             = $(subst .c,.o,$(SRC))

.INTERMEDIATE: $(OBJ)

all: test.so

%.o: %.c
    $(CC) $(CFLAGS) -o $@ -c $<

test.so: $(OBJ)
    $(CC) -shared $^ -o $@

clean:
    @rm -f *.o *~ *.so

我在同一目录中只有两个文件:a.c和b.c 当我执行“全部制作”时,我得到了以下完美的结果。

/home/utils/gcc-5.2.0/bin/g++ -Wall -Werror -fPIC  -o a.o -c a.c
/home/utils/gcc-5.2.0/bin/g++ -Wall -Werror -fPIC  -o b.o -c b.c
/home/utils/gcc-5.2.0/bin/g++ -shared a.o b.o -o test.so
rm a.o b.o

但是,如果我这样做:     触摸交流全部制作

我得到了与上面相同的make执行序列,这不是我期望的。 a.c和b.c之间没有依赖关系。我期望的是:

/home/utils/gcc-5.2.0/bin/g++ -Wall -Werror -fPIC  -o a.o -c a.c
/home/utils/gcc-5.2.0/bin/g++ -shared a.o b.o -o test.so
rm a.o

我不明白为什么再次编译b.c。根据gnumake手册:

第一个区别是如果中间文件不存在会发生什么。如果普通文件b不存在,并且make考虑依赖于b的目标,则它总是创建b,然后从b更新目标。但是,如果b是一个中间文件,那么make可以单独放置。除非b的某些先决条件比该目标新,或者有其他原因更新该目标,否则不会麻烦更新b或最终目标。

b.o是一个中间文件,不应更改它,因为b.c尚未更改。 我想念什么?

2 个答案:

答案 0 :(得分:0)

如果未重新编译b.o,则无法创建test.so,因为它依赖于b.o。如果a.o被删除,则无法从b.ob.o创建共享库。

如果您尝试创建静态库,则可以使用make的特殊存档语法替换a.o,而无需重新编译b.o,但这是不可能的,除非您使用存档特殊语法,并且对于共享库(例如您要在此处构建的库)无法完成。

有一个原因是默认情况下这些.o文件不被视为中间文件,通过添加.INTERMEDIATE目标强制将它们设置为中间文件并不意味着您可以避免重建它们。

答案 1 :(得分:0)

您的困难源于中间文件的含义 不是GNU Make的意思;但是.INTERMEDIATE特殊目标可让您坚持 a.ob.o(在您的makefile中)不是GNU Make的中间文件 在意义上,应视为它们在GNU Make的意义上是中间 ,并且 然后您会对后果感到惊讶。

F 的意思是中间文件 F是目标T和 F本身具有先决条件。因此a.ob.o相对于 test.so,因为它们是该目标的先决条件,并且自身具有 相应的先决条件a.c和b.c`。

从您的理解为中级,使目标test.so并不需要 (a.o | b.o)要重制,除非它早于(a.c|b.c)。所以你被事实吓了一跳 当将a.ob.o制成.INTERMEDIATE时,GNU Make 总是删除它们 当它变成test.so时:

  

b.o是一个中间文件,不应更改它,因为b.c尚未更改。我想念什么?

您很想念10.4 Chains of Implicit Rules

  

通常,如果文件在makefile中被提及为目标或先决条件,则该文件不能为中间文件。   但是,您可以通过将文件列出为特殊目标.INTERMEDIATE的先决条件来将其明确标记为中间文件。   即使以其他方式明确提及该文件,此操作也将生效。

因此,在GNU Make的意义上,a.ob.o通常不是是中间的, 因为它们在您的Makefile中被称为test.so的先决条件。但是你可以 按照您的意愿,通过将它们列为.INTERMEDIATE的先决条件,使它们被视为GNU Make的中间语言。

GNU Make通过 intermediate 意味着什么比您的意图更具有技术性, 手册的同一部分对此进行了说明:

  

有时可以通过一系列隐式规则来创建文件。例如,一个文件n.o   可以通过先运行Yacc然后运行cc由n.y生成。这样的序列称为链。

     

如果文件n.c存在或在makefile中提到,则不需要特殊搜索:   make查找可以通过n.c的C编译来创建目标文件;后来,当   考虑如何制作n.c,使用运行Yacc的规则。最终,n.c和n.o都会更新。

     

但是,即使n.c不存在并且没有被提及,make也知道如何将其设想为   n.o和n.y之间缺少链接! 在这种情况下,n.c被称为中间文件。   一旦make决定使用中间文件,该文件将作为以下内容输入数据库   如果在makefile文件中已提及它,以及说明如何创建它的隐式规则。

(我的重点)。简而言之,如果需要Make来生成目标 output ,并发现文件 input

  • 没有明确的规则可从 input 生成 output ,但-
  • 它知道由 implicit (又称​​ builtin )规则组成的序列 不是makefile中目标或必备条件的文件 stage 可以 由 input 制成,然后由 output 制成,则它将采用 通过 stage input 输入 output 的规则序列,而 stage 将是 中间文件。

至关重要的是,由于 stage 不是您的目标之一,也不是任何目标的先决条件,因此 知道这只是从 input 产生 output 一次性副产品, 因此可以在达到该目的后将其删除。

这是一个简单的示例项目,涉及的文件实际上是GNU Make的中间意思。 在项目目录中,我们有一些yaccyacc.ylexlex.l, 并且我们要使用以下makefile从它们parse中构建一个解析器:

制作文件

YFLAGS := -d
OBJS := yacc.o lex.o

.PHONY: all clean

all: parse

parse: $(OBJS)
    $(CC) $^ -o $@

clean:
    $(RM) parse $(OBJS)

运行方式:

$ make
yacc -d yacc.y
mv -f y.tab.c yacc.c
cc    -c -o yacc.o yacc.c
lex  -t lex.l > lex.c
cc    -c -o lex.o lex.c
cc yacc.o lex.o -o parse
rm lex.c yacc.c

现在,您会看到Make使用其隐式规则目录可以算出 yacc.o可以通过编译不存在的C源文件来完成 yacc.c,而该yacc.c可以从现有的源文件中产生 通过运行yacc.y yacc -d yacc.y; mv -f y.tab.c yacc.c。同样,它发现lex.o 可以通过编译不存在的C源lex.c来完成,而该lex.c 可以通过运行lex.llex -t lex.l > lex.c中创建。那么parse是 由yacc.olex.o通过makefile的常规链接制成 指定。

但是您的makefile对yacc.clex.c来说什么也没说。据,直到...为止 您已经告诉了Make,这些文件是否存在与您无关紧要。 它们是中间文件,仅处于.y -> .o.l -> .o生产中。所以 当用它们完成制作时:

rm lex.c yacc.c

然后,如果您:

$ touch yacc.y

再次:

$ make
yacc -d yacc.y
mv -f y.tab.c yacc.c
cc    -c -o yacc.o yacc.c
cc yacc.o lex.o -o parse
rm yacc.c

只有yacc.oparse one 的链接而被重制。 这次生成的中间文件yacc.c被删除。

底线

您的目标文件不是GNU Make的中间文件,您也不是 希望像对待他们一样对待他们。这对于目标文件是正常的。 因此,删除.INTERMEDIATE: $(OBJ)