我们的构建系统中有一个又长又复杂的 Makefile。有没有一种好方法可以准确跟踪给定 make 调用执行了哪些目标?
3 回答
ElectricMake can generate an XML-marked-up version of your build log with lots of information that would help in this situation:
- The full command-lines for all commands invoked during the build (even those that were marked as "silent" commands with the
@
modifier). - The origin (makefile and line number) of the commands invoked.
- Runtime of the commands.
- Dependency relationships between the targets in the build.
- Structural relationship between targets and recursive makes in the build.
- Files read/written by the commands invoked in the build.
Here's a sample of that output:
<job id="J0824ab08" thread="5e72bb0" node="linbuild1-2" type="rule" name="../../i686_Linux/testmain/testmain.d" file="../config/rules.mak" line="109">
<command line="110">
<argv>echo Rebuilding '../../i686_Linux/testmain/testmain.d'</argv>
<output src="prog">Rebuilding ../../i686_Linux/testmain/testmain.d
</output>
</command>
<command line="111-114">
<argv>set -e; g++ -MM -w -DUSE_PROFILING -DUSE_LOGGING -DHAVE_UNIX -DHAVE_LINUX -I.. testmain.cpp \
| sed 's!\(testmain\)\.o[ :]*!../../i686_Linux/testmain/\1.o '../../i686_Linux/testmain/testmain.d' : !g' \
> '../../i686_Linux/testmain/testmain.d'; \
[ -s '../../i686_Linux/testmain/testmain.d' ] || touch '../../i686_Linux/testmain/testmain.d'</argv>
</command>
<opList>
<op type="read" file="/home/ericm/src/testmain/testmain.cpp"/>
<op type="read" file="/home/ericm/src/minidumper/ExceptionReport.h"/>
<op type="read" file="/home/ericm/src/util/ECAssert.h"/>
<op type="create" file="/home/ericm/i686_Linux/ecloud/testmain/testmain.d" found="0"/>
</opList>
<timing invoked="1.919926" completed="3.600491" node="linbuild1-2"/>
<waitingJobs idList="J0824ae38"/>
</job>
How to Quickly Navigate an Unfamiliar Makefile shows an example of using the annotated build log to find your way around a makefile.
Data Mining ElectricAccelerator Annotation shows how you can use the annotated build log to generate a bill-of-materials for the build.
ElectricMake is GNU Make compatible, so it can process makefiles that work with GNU make.
Disclaimer: I'm the architect and lead developer of ElectricAccelerator.
You can get some information about what targets are being built and why by redefining the SHELL variable in GNU make:
__ORIGINAL_SHELL:=$(SHELL)
SHELL=$(warning Building $@$(if $<, (from $<))$(if $?, ($? newer)))$(TIME) $(__ORIGINAL_SHELL)
For example, in trace-targets.mk
:
__ORIGINAL_SHELL:=$(SHELL)
SHELL=$(warning Building $@$(if $<, (from $<))$(if $?, ($? newer)))$(TIME) $(__ORIGINAL_SHELL)
all: aa.stamp ba.stamp
%.stamp:
echo stamp > $(@)
stamp-clean:
rm -vf *.stamp
clean: stamp-clean
.PHONY: %.phony
%.phony:
echo $(@)
aa.stamp: ab.stamp
ab.stamp: ac.stamp
ba.stamp: bb.stamp
bb.stamp: bc.phony
Running trace-targets.mk
after clean:
$ make -f trace-targets.mk
trace-targets.mk:9: Building ac.stamp
echo stamp > ac.stamp
trace-targets.mk:9: Building ab.stamp (from ac.stamp) (ac.stamp newer)
echo stamp > ab.stamp
trace-targets.mk:9: Building aa.stamp (from ab.stamp) (ab.stamp newer)
echo stamp > aa.stamp
trace-targets.mk:18: Building bc.phony
echo bc.phony
bc.phony
trace-targets.mk:9: Building bb.stamp (from bc.phony) (bc.phony newer)
echo stamp > bb.stamp
trace-targets.mk:9: Building ba.stamp (from bb.stamp) (bb.stamp newer)
echo stamp > ba.stamp
Then running trace-targets.mk
again without cleaning:
$ make -f trace-targets.mk
trace-targets.mk:18: Building bc.phony
echo bc.phony
bc.phony
trace-targets.mk:9: Building bb.stamp (from bc.phony) (bc.phony newer)
echo stamp > bb.stamp
trace-targets.mk:9: Building ba.stamp (from bb.stamp) (bb.stamp newer)
echo stamp > ba.stamp
Practically, what I do in my makefiles is I add this snippet:
ifneq ($(filter all targets,$(VERBOSE)),)
__ORIGINAL_SHELL:=$(SHELL)
SHELL=$(warning Building $@$(if $<, (from $<))$(if $?, ($? newer)))$(TIME) $(__ORIGINAL_SHELL)
endif
Then I run my makefiles as follow to see the tracing:
make VERBOSE=all
# or
make VERBOSE=targets
The reason for the all/targets is because I also have other verbose stuff like this:
ifneq ($(filter all vars,$(VERBOSE)),)
dump_var=$(info var $(1)=$($(1)))
dump_vars=$(foreach var,$(1),$(call dump_var,$(var)))
else
dump_var=
dump_vars=
endif
# used like
$(call dump_vars,SHELL VERBOSE)
And sometimes I want to selectively enable/disable debugging aspects.
If you have many makefiles it may make sense to put this in a common file and include it from others.
Credit by John Graham-Cumming who described this method in Tracing rule execution in GNU Make.