我刚刚收到一个包含以下内容的 Makefile:
rpm: clean $(JARFILE)
# commands here...
您可能已经猜到了,
clean
目标会删除该 jar(如果存在)。这个 Makefile 正确吗?是否可以保证 clean
目标将在 JARFILE
目标之前运行?我问的不是风格,而是正确性。是否有可能建造 JARFILE
但随后却被 clean
目标吹走?
我正在使用 gnu make,所以这是我最感兴趣的;我不确定这是否是为了移植到其他风格的品牌。
如果您在没有启用任何并行性的情况下运行 make(没有
-j
),那么您的 makefile 将正常工作。如果串行运行,Make 确实保证先决条件按照它们在 makefile 中列出的顺序构建。
但是,如果您启用
-j
,那么两个目标可能会并行运行,然后您可能会遇到麻烦。
是否可以保证 clean 目标将在 JARFILE 目标之前运行?
make
一般:不,没有没有这样的保证。
GNU
make
:
对于您的特定规则,使用先决条件
clean $(JARFILE)
,Makefile 的其余部分不太可能描述依赖关系树,从而使 GNU 有理由在 $(JARFILE)
之前解决 clean
。
理论上这是可能的。例如。考虑一下这个
Makefile
:
# --- file: Makefile ---
rpm: clean A.jar
@$(rpm_build)
A.jar: A.java
cp $^ $@
@printf "[done: '$@']\n\n"
clean: A.jar
@# Error msg if attempting to delete non-existing file, so I
@# I made sure it will exist before rm-ing it by adding A.jar
@# as a prerequisite to `clean` above. //consultant
rm A.jar
@printf "[done: '$@']\n\n"
rpm_build = @[ -e $(JARFILE) ] && echo "rpm: OK." \
|| echo "rpm: ERR: no '$(JARFILE)'"
尝试制定
rpm
目标:
$ touch A.java
$ gmake
cp A.java A.jar
[done: 'A.jar']
rm A.jar
[done: 'clean']
rpm: ERR: no 'A.jar'
Makefile
的依赖图通过构建A.jar
,然后做clean
,然后构建rpm
来解决。这样,所有先决条件都得到满足 - GNU make 并不是由于 rpm
目标失败(其他人)而导致公司构建系统崩溃的错误。
最好通过依赖关系图来处理对解决先决条件的顺序的任何限制。这更清晰(不是每个人都知道 GNU make 的复杂性),使 Makefile(更)可移植,并且更健壮 [2]。
下面是一个例子。我添加了中间目标
A_clean_jar
,因此我们仍然有可能在不首先触发 A.jar
的情况下构建 clean
。
# --- file: Makefile ---
rpm: A_clean_jar
@$(inspect_jar)
A_clean_jar: A.java clean
$(build_jar)
@printf '[done: created pristine jar]\n\n'
A.jar: A.java
$(build_jar)
@printf '[done: created jar]\n\n'
clean:
@rm -f A.jar
@printf '[done: jar cleaned out of existence]\n\n'
build_jar = cp A.java A.jar
inspect_jar = @[ -e A.jar ] && printf "rpm: OK.\n\n" \
|| printf "rpm: ERR: no 'A.jar'\n\n"
我们来试试:
$ touch A.java
$ gmake rpm
[done: jar cleaned out of existence]
cp A.java A.jar
[done: created pristine jar]
rpm: OK.
$ # so: first `clean`, then build A.jar, then the rpm target - all well.
$ # now: build target A.jar - no preceding `clear` expected:
$ touch A.java
$ gmake A.jar
cp A.java A.jar
[done: created jar]
$ # check.
几个月后,首席技术官在注意到数据中心账单略有增加后浏览了回购协议。一个看似无关的部门的测试工程师发布的提交引起了他的注意,据称该提交负责维护内部网遗留的 PHP,有时还用 Excel 脚本帮助会计助理的秘书。认识到提交是对不久前参与构建系统破坏的同一个文件进行的,导致即将到来的发布活动出现昂贵的延迟,他查看了 - 并发现了这一点:
#clean
clean: A.jar
@# Added back my `clean: A.jar` above - this time I tested it
@# before pushing, and now nothing crashed. // your guy
rm A.jar
@# rm -f A.jar (avoid -f with rm - dangerous)
@printf "[done: '$@']\n\n"
过了一会儿,CTO 的一位同事在他的屏幕上注意到了这一点:
$ # let's just see how this behaves
$ gmake
java.c A.java && jar cvf A.jar A.class
[done: created jar]
[done: jar cleaned out of existence]
java.c A.java && jar cvf A.jar A.class
[done: created pristine jar]
rpm: OK.
$ less Makefile
$ git revert HEAD -m "reverting: prev. commit caused an unneccessary "\
"build (jar created twice), but nothing worse than that this time"
$ git log -2 --pretty=format:'%ae'
[email protected]
$ gh api -X DELETE /repos/admin/spacex-padctrl-prod/collaborators/[email protected]
$ >> ~/notesToSelf.txt echo 'avoid long-duration consultant contracts'
脚注
[1]至少MadScientist的其他答案表明了这一点(他是GNU make的维护者)