Commit 788cad88 authored by Michael Shigorin's avatar Michael Shigorin

initial target tracing support and reporting

The purpose is being able to examine particular target interdependency graph for a given image having been configured to avoid convoluted dependencies (loops in particular). The implementation is based on SHELL hook hint by John Graham-Cumming: http://cmcrossroads.com/ask-mr-make/6535-tracing-rule-execution-in-gnu-make
parent a52b7476
# umbrella mkimage-profiles makefile: # umbrella mkimage-profiles makefile:
# iterate over multiple goals/arches # iterate over multiple goals/arches,
# collect proceedings
# immediate assignment # for immediate assignment
ifndef ARCHES ifndef ARCHES
ifdef ARCH ifdef ARCH
ARCHES := $(ARCH) ARCHES := $(ARCH)
...@@ -11,8 +12,14 @@ endif ...@@ -11,8 +12,14 @@ endif
endif endif
export ARCHES export ARCHES
# supervise target tracing; leave stderr alone
ifdef REPORT
export REPORT_PATH := $(shell mktemp --tmpdir mkimage-profiles.report.XXXXXXX)
POSTPROC := | bin/report-filter > $(REPORT_PATH)
endif
# recursive make considered useful for m-p # recursive make considered useful for m-p
MAKE += --no-print-directory MAKE += -r --no-print-directory
.PHONY: clean distclean help .PHONY: clean distclean help
clean distclean help: clean distclean help:
...@@ -23,16 +30,18 @@ export NUM_TARGETS := $(words $(MAKECMDGOALS)) ...@@ -23,16 +30,18 @@ export NUM_TARGETS := $(words $(MAKECMDGOALS))
# real targets need real work # real targets need real work
%: %:
@n=1; \ @n=1; \
say() { echo "$$@" >&2; }; \
if [ "$(NUM_TARGETS)" -gt 1 ]; then \ if [ "$(NUM_TARGETS)" -gt 1 ]; then \
n="`echo $(MAKECMDGOALS) \ n="`echo $(MAKECMDGOALS) \
| tr '[[:space:]]' '\n' \ | tr '[[:space:]]' '\n' \
| grep -nx "$@" \ | grep -nx "$@" \
| cut -d: -f1`"; \ | cut -d: -f1`"; \
echo "** goal: $@ [$$n/$(NUM_TARGETS)]"; \ say "** goal: $@ [$$n/$(NUM_TARGETS)]"; \
fi; \ fi; \
for ARCH in $(ARCHES); do \ for ARCH in $(ARCHES); do \
if [ "$$ARCH" != "$(firstword $(ARCHES))" ]; then echo; fi; \ if [ "$$ARCH" != "$(firstword $(ARCHES))" ]; then say; fi; \
echo "** ARCH: $$ARCH"; \ say "** ARCH: $$ARCH" >&2; \
$(MAKE) -f main.mk ARCH=$$ARCH $@; \ $(MAKE) -f main.mk ARCH=$$ARCH $@ $(POSTPROC); \
$(MAKE) -f reports.mk ARCH=$$ARCH; \
done; \ done; \
if [ "$$n" -lt "$(NUM_TARGETS)" ]; then echo; fi if [ "$$n" -lt "$(NUM_TARGETS)" ]; then say; fi
#!/bin/sh
# filter worker make stdout for report scripts
grep '^trace:building ' | uniq
#!/bin/sh
echo "digraph {"
echo " { node [fontname=Helvetica,fontsize=20];"
while read first second third rest; do
FROM=; TO=
case "$first" in
"trace:building")
case "$third" in
"->")
FROM="$second"; TO="$rest";;
*)
continue;;
esac
;;
*)
continue;;
esac
[ -n "$FROM" -a -n "$TO" ] || continue
for to in $TO; do
out=" \"$FROM\" -> \"$to\""
case $to in
*distro/*)
echo "$out [weight=10];";;
*)
echo "$out";;
esac
done
done
echo " }"
echo "}"
#!/bin/sh
DISTCFG=build/distcfg.mk
[ -s "$DISTCFG" ] || exit 1
VARIABLES=
echo "graph { rankdir=LR;"
echo " { node [height=.1,width=.3,fontname=Helvetica,fontsize=10];"
feat_vars()
{
FEATURE=
while read first second rest; do
case "$first" in
\#[A-Z]*)
continue;; # overridden feature
\#) # feature mark
case "$second" in
profile/*)
FEATURE=;;
*)
FEATURE="$second";;
esac
;;
*)
case "$second" in
=|+=|?=)
case "$first" in
DISTCFG_MK|SUBPROFILES|FEATURES|IMAGE*|MKIMAGE_*)
continue;;
*)
VAR="$first"
VARIABLES="$VARIABLES; $VAR"
;;
esac
;;
*)
continue;;
esac
;;
esac
[ -n "$FEATURE" -a -n "$VAR" ] || continue
echo " \"$FEATURE\" -- \"$VAR\";"
done < "$DISTCFG"
echo " { node [shape=box]$VARIABLES; }"
}
feat_vars | LC_COLLATE=C sort -ru
echo " }"
echo "}"
...@@ -3,6 +3,12 @@ ...@@ -3,6 +3,12 @@
NB: пути приводятся от верхнего уровня; проект в целом предполагает NB: пути приводятся от верхнего уровня; проект в целом предполагает
GNU make 3.81 (с использованием которого и разрабатывается). GNU make 3.81 (с использованием которого и разрабатывается).
- lib/report.mk
+ ожидает, что каждая подлежащая трассированию цель каждого
makefile при сборке конфигурации образа содержит непустой
recipe -- хотя бы "; @:" -- т.к. зависит от запуска $(SHELL)
+ характерный признак пропуска -- разрыв графа (report-targets.png)
- pkg.in/lists/Makefile - pkg.in/lists/Makefile
+ ожидает, что названия пакаджлистов указываются в переменных + ожидает, что названия пакаджлистов указываются в переменных
вида *_LISTS, и копирует в генерируемый профиль только их вида *_LISTS, и копирует в генерируемый профиль только их
......
...@@ -65,6 +65,11 @@ ...@@ -65,6 +65,11 @@
+ значение: пусто (по умолчанию) либо любая строка + значение: пусто (по умолчанию) либо любая строка
+ см. ../lib/build.mk + см. ../lib/build.mk
- REPORT
+ запрашивает создание отчёта о собранном образе
+ значение: пусто (по умолчанию) либо любая строка
+ см. ../Makefile, ../report.mk, ../lib/report.mk
- SAVE_PROFILE - SAVE_PROFILE
+ сохраняет архив сгенерированного профиля в .disk/ + сохраняет архив сгенерированного профиля в .disk/
+ значение: пусто (по умолчанию) либо любая строка + значение: пусто (по умолчанию) либо любая строка
......
...@@ -31,8 +31,10 @@ IMAGEDIR ?= $(shell \ ...@@ -31,8 +31,10 @@ IMAGEDIR ?= $(shell \
) )
# actual build starter # actual build starter
# NB: our output MUST go into stderr to escape POSTPROC
build-image: profile/populate build-image: profile/populate
@if [ -n "$(CHECK)" ]; then \ @{ \
if [ -n "$(CHECK)" ]; then \
echo "$(TIME) skipping actual image build (CHECK is set)"; \ echo "$(TIME) skipping actual image build (CHECK is set)"; \
exit; \ exit; \
fi; \ fi; \
...@@ -61,4 +63,5 @@ build-image: profile/populate ...@@ -61,4 +63,5 @@ build-image: profile/populate
df -P $(BUILDDIR) | awk 'END { if ($$4 < $(LOWSPACE)) \ df -P $(BUILDDIR) | awk 'END { if ($$4 < $(LOWSPACE)) \
{ print "NB: low space on "$$6" ("$$5" used)"}}'; \ { print "NB: low space on "$$6" ("$$5" used)"}}'; \
fi; \ fi; \
if [ -n "$(BELL)" ]; then echo -ne '\a' >&2; fi if [ -n "$(BELL)" ]; then echo -ne '\a'; fi; \
} >&2
...@@ -19,19 +19,23 @@ endif ...@@ -19,19 +19,23 @@ endif
endif endif
# ordinary clean: destroys workdirs but not the corresponding results # ordinary clean: destroys workdirs but not the corresponding results
# NB: our output MUST go into stderr to escape POSTPROC
clean: clean:
@find -name '*~' -delete >&/dev/null ||: @{ \
@if [ -L "$(SYMLINK)" -a -d "$(SYMLINK)"/ ]; then \ find -name '*~' -delete >&/dev/null ||:; \
if [ -L "$(SYMLINK)" -a -d "$(SYMLINK)"/ ]; then \
echo "$(TIME) cleaning up $(WARNING)"; \ echo "$(TIME) cleaning up $(WARNING)"; \
$(MAKE) -C "$(SYMLINK)" $@ \ $(MAKE) -C "$(SYMLINK)" $@ \
GLOBAL_BUILDDIR="$(realpath $(SYMLINK))" $(LOG) ||:; \ GLOBAL_BUILDDIR="$(realpath $(SYMLINK))" $(LOG) ||:; \
fi fi; \
} >&2
# there can be some sense in writing log here even if normally # there can be some sense in writing log here even if normally
# $(BUILDDIR)/ gets purged: make might have failed, # $(BUILDDIR)/ gets purged: make might have failed,
# and BUILDLOG can be specified by hand either # and BUILDLOG can be specified by hand either
distclean: clean distclean: clean
@if [ -L "$(SYMLINK)" -a -d "$(SYMLINK)"/ ]; then \ @{ \
if [ -L "$(SYMLINK)" -a -d "$(SYMLINK)"/ ]; then \
build="$(realpath $(SYMLINK)/)"; \ build="$(realpath $(SYMLINK)/)"; \
if [ "$$build" = / ]; then \ if [ "$$build" = / ]; then \
echo "** ERROR: invalid \`"$(SYMLINK)"' symlink" >&2; \ echo "** ERROR: invalid \`"$(SYMLINK)"' symlink" >&2; \
...@@ -41,16 +45,19 @@ distclean: clean ...@@ -41,16 +45,19 @@ distclean: clean
GLOBAL_BUILDDIR="$$build" $(LOG) ||: \ GLOBAL_BUILDDIR="$$build" $(LOG) ||: \
rm -rf "$$build"; \ rm -rf "$$build"; \
fi; \ fi; \
fi fi; \
@rm -f "$(SYMLINK)" rm -f "$(SYMLINK)"; \
} >&2
# builddir existing outside read-only metaprofile is less ephemeral # builddir existing outside read-only metaprofile is less ephemeral
# than BUILDDIR is -- usually it's unneeded afterwards so just zap it # than BUILDDIR is -- usually it's unneeded afterwards so just zap it
postclean: build-image postclean: build-image
@if [ "$(NUM_TARGETS)" -gt 1 -a -z "$(DEBUG)" ] || \ @{ \
if [ "$(NUM_TARGETS)" -gt 1 -a -z "$(DEBUG)" ] || \
[ ! -L "$(SYMLINK)" -a "0$(DEBUG)" -lt 2 ]; then \ [ ! -L "$(SYMLINK)" -a "0$(DEBUG)" -lt 2 ]; then \
echo "$(TIME) cleaning up after build"; \ echo "$(TIME) cleaning up after build"; \
$(MAKE) -C "$(BUILDDIR)" distclean \ $(MAKE) -C "$(BUILDDIR)" distclean \
GLOBAL_BUILDDIR="$(BUILDDIR)" $(LOG) ||:; \ GLOBAL_BUILDDIR="$(BUILDDIR)" $(LOG) ||:; \
rm -rf "$(BUILDDIR)"; \ rm -rf "$(BUILDDIR)"; \
fi fi; \
} >&2
...@@ -34,50 +34,57 @@ CONFIG := $(BUILDDIR)/distcfg.mk ...@@ -34,50 +34,57 @@ CONFIG := $(BUILDDIR)/distcfg.mk
RC := $(HOME)/.mkimage/profiles.mk RC := $(HOME)/.mkimage/profiles.mk
# step 1: initialize the off-tree mkimage profile (BUILDDIR) # step 1: initialize the off-tree mkimage profile (BUILDDIR)
# NB: our output MUST go into stderr to escape POSTPROC
profile/init: distclean profile/init: distclean
@if [ "`realpath "$(BUILDDIR)/"`" = / ]; then \ @{ \
if [ "`realpath "$(BUILDDIR)/"`" = / ]; then \
echo "$(TIME) ERROR: invalid BUILDDIR: \`$(BUILDDIR)'"; \ echo "$(TIME) ERROR: invalid BUILDDIR: \`$(BUILDDIR)'"; \
exit 128; \ exit 128; \
fi; fi; \
@echo -n "$(TIME) initializing BUILDDIR: " echo -n "$(TIME) initializing BUILDDIR: "; \
@rsync -qaxH --delete-after image.in/ "$(BUILDDIR)"/ rsync -qaxH --delete-after image.in/ "$(BUILDDIR)"/; \
@mkdir "$(BUILDDIR)"/.mki # mkimage toplevel marker mkdir "$(BUILDDIR)"/.mki; \
} >&2
@$(call put,ifndef DISTCFG_MK) @$(call put,ifndef DISTCFG_MK)
@$(call put,DISTCFG_MK = 1) @$(call put,DISTCFG_MK = 1)
@if type -t git >&/dev/null; then \ @{ \
if type -t git >&/dev/null; then \
if [ -d .git ]; then \ if [ -d .git ]; then \
git show-ref --head -d -s -- HEAD && \ git show-ref --head -d -s -- HEAD && \
git status -s && \ git status -s && \
echo; \ echo; \
fi $(LOG); \ fi $(LOG); \
fi fi; \
@{ \ { \
eval `apt-config shell $${APTCONF:+-c=$(wildcard $(APTCONF))} \ eval `apt-config shell $${APTCONF:+-c=$(wildcard $(APTCONF))} \
SOURCELIST Dir::Etc::sourcelist/f \ SOURCELIST Dir::Etc::sourcelist/f \
SOURCEPARTS Dir::Etc::sourceparts/d`; \ SOURCEPARTS Dir::Etc::sourceparts/d`; \
find "$$SOURCEPARTS" -name '*.list' \ find "$$SOURCEPARTS" -name '*.list' \
| xargs egrep -Rhv '^#|^[[:blank:]]*$$' "$$SOURCELIST" && \ | xargs egrep -Rhv '^#|^[[:blank:]]*$$' "$$SOURCELIST" && \
echo; \ echo; \
} $(LOG) } $(LOG); \
@if type -t git >&/dev/null; then \ if type -t git >&/dev/null; then \
if cd $(BUILDDIR); then \ if cd $(BUILDDIR); then \
git init -q && \ git init -q && \
git add . && \ git add . && \
git commit -qam 'derivative profile initialized'; \ git commit -qam 'derivative profile initialized'; \
cd ->&/dev/null; \
fi; \ fi; \
fi fi; \
@if [ -w . ]; then \ if [ -w . ]; then \
rm -f "$(SYMLINK)" && \ rm -f "$(SYMLINK)" && \
ln -s "$(BUILDDIR)" "$(SYMLINK)" && \ ln -s "$(BUILDDIR)" "$(SYMLINK)" && \
echo "$(SYMLINK)/"; \ echo "$(SYMLINK)/"; \
else \ else \
echo "$(BUILDDIR)/" $(SHORTEN); \ echo "$(BUILDDIR)/"; \
fi $(SHORTEN) fi $(SHORTEN); \
} >&2
profile/bare: profile/init profile/bare: profile/init
@NOTE="$${GLOBAL_VERBOSE:+: $(CONFIG)}"; \ @{ \
echo "$(TIME) preparing distro config$$NOTE" \ NOTE="$${GLOBAL_VERBOSE:+: $(CONFIG)}"; \
$(SHORTEN) echo "$(TIME) preparing distro config$$NOTE" $(SHORTEN); \
} >&2
@$(call try,MKIMAGE_PREFIX,/usr/share/mkimage) @$(call try,MKIMAGE_PREFIX,/usr/share/mkimage)
@$(call try,GLOBAL_VERBOSE,) @$(call try,GLOBAL_VERBOSE,)
@$(call try,IMAGEDIR,$(IMAGEDIR)) @$(call try,IMAGEDIR,$(IMAGEDIR))
...@@ -106,7 +113,7 @@ profile/dump-vars: ...@@ -106,7 +113,7 @@ profile/dump-vars:
fi $(LOG) fi $(LOG)
# step 3 entry point: copy the needed parts into BUILDDIR # step 3 entry point: copy the needed parts into BUILDDIR
profile/populate: profile/init profile/finalize profile/dump-vars profile/populate: profile/finalize profile/dump-vars
@for dir in sub.in features.in pkg.in; do \ @for dir in sub.in features.in pkg.in; do \
$(MAKE) -C $$dir $(LOG); \ $(MAKE) -C $$dir $(LOG); \
done done
# enable make target tracing
ifdef REPORT
TRACE_PREFIX := trace:building
OLD_SHELL := $(SHELL)
SHELL = $(info $(TRACE_PREFIX) $@$(if $^$|, -> $^ $|))$(OLD_SHELL)
# piggyback BUILDDIR back into supervising environment
$(info $(TRACE_PREFIX) BUILDDIR = $(BUILDDIR))
endif
# collect what's left
all: reports/targets
reports/targets:
@if [ -n "$$REPORT_PATH" -a -s "$$REPORT_PATH" ]; then \
BUILDDIR="`sed -n 's/^.* BUILDDIR = \(.*\)/\1/p' \
"$$REPORT_PATH"`"; \
REPORT_IMAGE="$$BUILDDIR/targets.png"; \
bin/report-targets < "$$REPORT_PATH" \
| dot -Tpng -o "$$REPORT_IMAGE" \
&& echo "** target graph report: $$REPORT_IMAGE" \
&& mv "$$REPORT_PATH" "$$BUILDDIR/targets.log"; \
fi
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment