Make/makefile进度指示!

Gio*_*hal 25 build-automation scripting makefile progress-bar

看看这个makefile,它有一些原始的进度指示(可能是一个进度条).

请给我建议/意见!



# BUILD is initially undefined
ifndef BUILD

# max equals 256 x's
sixteen := x x x x x x x x x x x x x x x x
MAX := $(foreach x,$(sixteen),$(sixteen))

# T estimates how many targets we are building by replacing BUILD with a special string
T := $(shell $(MAKE) -nrRf $(firstword $(MAKEFILE_LIST)) $(MAKECMDGOALS) \
            BUILD="COUNTTHIS" | grep -c "COUNTTHIS")

# N is the number of pending targets in base 1, well in fact, base x :-)
N := $(wordlist 1,$T,$(MAX))

# auto-decrementing counter that returns the number of pending targets in base 10
counter = $(words $N)$(eval N := $(wordlist 2,$(words $N),$N))

# BUILD is now defined to show the progress, this also avoids redefining T in loop
BUILD = @echo $(counter) of $(T)
endif

# dummy phony targets

.PHONY: all clean

all: target
    @echo done

clean:
    @rm -f target *.c

# dummy build rules

target: a.c b.c c.c d.c e.c f.c g.c
    @touch $@
    $(BUILD)

%.c:
    @touch $@
    $(BUILD)


欢迎所有建议!

Gio*_*hal 9

这个更少侵入性,更令人敬畏.

ifneq ($(words $(MAKECMDGOALS)),1)
.DEFAULT_GOAL = all
%:
        @$(MAKE) $@ --no-print-directory -rRf $(firstword $(MAKEFILE_LIST))
else
ifndef ECHO
T := $(shell $(MAKE) $(MAKECMDGOALS) --no-print-directory \
      -nrRf $(firstword $(MAKEFILE_LIST)) \
      ECHO="COUNTTHIS" | grep -c "COUNTTHIS")

N := x
C = $(words $N)$(eval N := x $N)
ECHO = echo "`expr " [\`expr $C '*' 100 / $T\`" : '.*\(....\)$$'`%]"
endif

.PHONY: all clean

all: target
        @$(ECHO) All done

clean:
        @rm -f target *.c
#       @$(ECHO) Clean done

target: a.c b.c c.c d.c e.c
        @$(ECHO) Linking $@
        @sleep 0.1
        @touch $@

%.c:
        @$(ECHO) Compiling $@
        @sleep 0.1
        @touch $@

endif
Run Code Online (Sandbox Code Playgroud)

  • 这很好,但是使用 -j 选项时百分比会中断。例如 [28%] 编译 bc [14%] 编译 ac ... (2认同)

che*_*kow 7

并没有真正的问题,所以这不是一个独立的答案,而是对 Giovanni Funchai 解决方案的扩展。这个问题是“GNU Make Progress”的第一个谷歌结果,所以我最终在这里寻找如何做到这一点。

\n\n

正如 Rob Wells 所指出的,该解决方案不适用于 <10%,但该技术可以通过帮助程序脚本以任何您认为对于您的构建而言足够可移植的语言完成的打印格式来扩展。例如,使用 python 帮助程序脚本:

\n\n

echo_progress.py:

\n\n
"""\nPrint makefile progress\n"""\n\nimport argparse\nimport math\nimport sys\n\ndef main():\n  parser = argparse.ArgumentParser(description=__doc__)\n  parser.add_argument("--stepno", type=int, required=True)\n  parser.add_argument("--nsteps", type=int, required=True)\n  parser.add_argument("remainder", nargs=argparse.REMAINDER)\n  args = parser.parse_args()\n\n  nchars = int(math.log(args.nsteps, 10)) + 1\n  fmt_str = "[{:Xd}/{:Xd}]({:6.2f}%)".replace("X", str(nchars))\n  progress = 100 * args.stepno / args.nsteps\n  sys.stdout.write(fmt_str.format(args.stepno, args.nsteps, progress))\n  for item in args.remainder:\n    sys.stdout.write(" ")\n    sys.stdout.write(item)\n  sys.stdout.write("\\n")\n\nif __name__ == "__main__":\n  main()\n
Run Code Online (Sandbox Code Playgroud)\n\n

和修改后的Makefile

\n\n
_mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))\nI := $(patsubst %/,%,$(dir $(_mkfile_path)))\n\nifneq ($(words $(MAKECMDGOALS)),1)\n.DEFAULT_GOAL = all\n%:\n    @$(MAKE) $@ --no-print-directory -rRf $(firstword $(MAKEFILE_LIST))\nelse\nifndef ECHO\nT := $(shell $(MAKE) $(MAKECMDGOALS) --no-print-directory \\\n      -nrRf $(firstword $(MAKEFILE_LIST)) \\\n      ECHO="COUNTTHIS" | grep -c "COUNTTHIS")\nN := x\nC = $(words $N)$(eval N := x $N)\nECHO = python $(I)/echo_progress.py --stepno=$C --nsteps=$T\nendif\n\n.PHONY: all clean\n\nall: target\n    @$(ECHO) All done\n\nclean:\n    @rm -f target *.c\n#       @$(ECHO) Clean done\n\ntarget: a.c b.c c.c d.c e.c f.c g.c h.c i.c j.c k.c l.c m.c n.c o.c p.c q.c \\\n        r.c s.c t.c u.c v.c w.c x.c y.c z.c\n    @$(ECHO) Linking $@\n    @sleep 0.01\n    @touch $@\n\n%.c:\n    @$(ECHO) Compiling $@\n    @sleep 0.01\n    @touch $@\n\nendif\n
Run Code Online (Sandbox Code Playgroud)\n\n

产量:

\n\n
$ make\n[ 1/28](  3.57%) Compiling a.c\n[ 2/28](  7.14%) Compiling b.c\n[ 3/28]( 10.71%) Compiling c.c\n[ 4/28]( 14.29%) Compiling d.c\n[ 5/28]( 17.86%) Compiling e.c\n[ 6/28]( 21.43%) Compiling f.c\n[ 7/28]( 25.00%) Compiling g.c\n[ 8/28]( 28.57%) Compiling h.c\n[ 9/28]( 32.14%) Compiling i.c\n[10/28]( 35.71%) Compiling j.c\n[11/28]( 39.29%) Compiling k.c\n[12/28]( 42.86%) Compiling l.c\n[13/28]( 46.43%) Compiling m.c\n[14/28]( 50.00%) Compiling n.c\n[15/28]( 53.57%) Compiling o.c\n[16/28]( 57.14%) Compiling p.c\n[17/28]( 60.71%) Compiling q.c\n[18/28]( 64.29%) Compiling r.c\n[19/28]( 67.86%) Compiling s.c\n[20/28]( 71.43%) Compiling t.c\n[21/28]( 75.00%) Compiling u.c\n[22/28]( 78.57%) Compiling v.c\n[23/28]( 82.14%) Compiling w.c\n[24/28]( 85.71%) Compiling x.c\n[25/28]( 89.29%) Compiling y.c\n[26/28]( 92.86%) Compiling z.c\n[27/28]( 96.43%) Linking target\n[28/28](100.00%) All done\n
Run Code Online (Sandbox Code Playgroud)\n\n

人们甚至可以用 unicode 字符打印一个精美的进度条。

\n\n

修改的echo_progress.py

\n\n
"""\nPrint makefile progress\n"""\n\nimport argparse\nimport math\nimport sys\n\ndef get_progress_bar(numchars, fraction=None, percent=None):\n  """\n  Return a high resolution unicode progress bar\n  """\n  if percent is not None:\n    fraction = percent / 100.0\n\n  if fraction >= 1.0:\n    return "\xe2\x96\x88" * numchars\n\n  blocks = [" ", "\xe2\x96\x8f", "\xe2\x96\x8e", "\xe2\x96\x8d", "\xe2\x96\x8c", "\xe2\x96\x8b", "\xe2\x96\x8a", "\xe2\x96\x89", "\xe2\x96\x88"]\n  length_in_chars = fraction * numchars\n  n_full = int(length_in_chars)\n  i_partial = int(8 * (length_in_chars - n_full))\n  n_empty = max(numchars - n_full - 1, 0)\n  return ("\xe2\x96\x88" * n_full) + blocks[i_partial] + (" " * n_empty)\n\ndef main():\n  parser = argparse.ArgumentParser(description=__doc__)\n  parser.add_argument("--stepno", type=int, required=True)\n  parser.add_argument("--nsteps", type=int, required=True)\n  parser.add_argument("remainder", nargs=argparse.REMAINDER)\n  args = parser.parse_args()\n\n  nchars = int(math.log(args.nsteps, 10)) + 1\n  fmt_str = "\\r[{:Xd}/{:Xd}]({:6.2f}%) ".replace("X", str(nchars))\n  progress = 100 * args.stepno / args.nsteps\n  sys.stdout.write(fmt_str.format(args.stepno, args.nsteps, progress))\n  sys.stdout.write(get_progress_bar(20, percent=progress))\n  remainder_str = " ".join(args.remainder)\n  sys.stdout.write(" {:20s}".format(remainder_str[:20]))\n  if args.stepno == args.nsteps:\n    sys.stdout.write("\\n")\n\nif __name__ == "__main__":\n  main()\n
Run Code Online (Sandbox Code Playgroud)\n\n

这会导致这样的结果:

\n\n
$ make clean && make\n[12/28]( 42.86%) \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x8a             Compiling k.c\n
Run Code Online (Sandbox Code Playgroud)\n\n

在进展过程中以及:

\n\n
$ make clean && make\n[28/28](100.00%) \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 All done \n
Run Code Online (Sandbox Code Playgroud)\n\n

完成后。

\n


phy*_*att 6

这是对@GiovanniFunchal 出色回答的轻微修改。

所以我想更好地理解这一点并让它在 < 10% 的情况下工作,所以我深入研究了文档并了解了更多关于expr 的信息

# PLACE AT THE TOP OF YOUR MAKEFILE
#---------------------------------
# Progress bar defs
#--------------------------------
#  words = count the number of words
ifneq ($(words $(MAKECMDGOALS)),1) # if no argument was given to make...
.DEFAULT_GOAL = all # set the default goal to all
#  http://www.gnu.org/software/make/manual/make.html
#  $@ = target name
#  %: = last resort recipe
#  --no-print-directory = don't print enter/leave messages for each output grouping
#  MAKEFILE_LIST = has a list of all the parsed Makefiles that can be found *.mk, Makefile, etc
#  -n = dry run, just print the recipes
#  -r = no builtin rules, disables implicit rules
#  -R = no builtin variables, disables implicit variables
#  -f = specify the name of the Makefile
%:                   # define a last resort default rule
      @$(MAKE) $@ --no-print-directory -rRf $(firstword $(MAKEFILE_LIST)) # recursive make call, 
else
ifndef ECHO
#  execute a dry run of make, defining echo beforehand, and count all the instances of "COUNTTHIS"
T := $(shell $(MAKE) $(MAKECMDGOALS) --no-print-directory \
      -nrRf $(firstword $(MAKEFILE_LIST)) \
      ECHO="COUNTTHIS" | grep -c "COUNTTHIS")
#  eval = evaluate the text and read the results as makefile commands
N := x
#  Recursively expand C for each instance of ECHO to count more x's
C = $(words $N)$(eval N := x $N)
#  Multipy the count of x's by 100, and divide by the count of "COUNTTHIS"
#  Followed by a percent sign
#  And wrap it all in square brackets
ECHO = echo -ne "\r [`expr $C '*' 100 / $T`%]"
endif
#------------------
# end progress bar
#------------------

# REST OF YOUR MAKEFILE HERE

#----- Progressbar endif at end Makefile
endif
Run Code Online (Sandbox Code Playgroud)

我摆脱了那: '.*\(....\)$$'部分。它会返回内部expr命令的最后 4 个字符,但如果它小于 4,则会失败。现在它适用于低于 10%!

这是免费评论版本:

ifneq ($(words $(MAKECMDGOALS)),1) # if no argument was given to make...
.DEFAULT_GOAL = all # set the default goal to all
%:                   # define a last resort default rule
      @$(MAKE) $@ --no-print-directory -rRf $(firstword $(MAKEFILE_LIST)) # recursive make call, 
else
ifndef ECHO
T := $(shell $(MAKE) $(MAKECMDGOALS) --no-print-directory \
      -nrRf $(firstword $(MAKEFILE_LIST)) \
      ECHO="COUNTTHIS" | grep -c "COUNTTHIS")
N := x
C = $(words $N)$(eval N := x $N)
ECHO = echo -ne "\r [`expr $C '*' 100 / $T`%]"
endif

# ...

endif
Run Code Online (Sandbox Code Playgroud)

希望有帮助。


And*_*rei 5

非常感谢@Giovanni Funchal@phyatt的提问和回答!

我只是为了自己更好的理解而将其进一步简化。

ifndef ECHO
HIT_TOTAL != ${MAKE} ${MAKECMDGOALS} --dry-run ECHO="HIT_MARK" | grep -c "HIT_MARK"
HIT_COUNT = $(eval HIT_N != expr ${HIT_N} + 1)${HIT_N}
ECHO = echo "[`expr ${HIT_COUNT} '*' 100 / ${HIT_TOTAL}`%]"
endif
Run Code Online (Sandbox Code Playgroud)
  • !=从 shell 命令分配
  • = 每次使用变量时都会对其进行评估
  • eval执行其参数但没有任何输出
  • expr允许进行算术计算

(不确定哪种方法更快:使用 expr 调用 shell 或使用 make 计算 'x'-es 。)

用法是一样的:

target:
    @$(ECHO) $@
Run Code Online (Sandbox Code Playgroud)

  • 很高兴在我最初发表文章 10 年后看到这篇文章仍然受到关注;) (2认同)