28

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

请给我建议/意见!


# BUILD 最初是未定义的
ifndef 构建

# 最大值等于 256 x
十六 := xxxxxxxxxxxxxxxx
MAX := $(foreach x,$(十六),$(十六))

# T 通过用特殊字符串替换 BUILD 来估计我们正在构建的目标数量
T := $(shell $(MAKE) -nrRf $(firstword $(MAKEFILE_LIST)) $(MAKECMDGOALS) \
            建立=“计数”| grep -c "计数")

# N 是基数为 1 的待处理目标的数量,实际上,基数 x :-)
N := $(单词表 1,$T,$(MAX))

# 自动递减计数器,返回以 10 为底的挂起目标的数量
计数器 = $(words $N)$(eval N := $(wordlist 2,$(words $N),$N))

# 现在定义BUILD来显示进度,这也避免了在循环中重新定义T
构建 = @echo $(counter) of $(T)
万一

# 虚拟目标

.PHONY:全部干净

全部:目标
    @echo 完成

干净的:
    @rm -f 目标 *.c

# 虚拟构建规则

目标:ac bc cc dc ec fc gc
    @触摸 $@
    $(建立)

%。C:
    @触摸 $@
    $(建立)


欢迎所有建议!

4

5 回答 5

11

这个不那么侵入性,而且更棒。

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
于 2009-01-18T16:19:54.743 回答
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

我摆脱了这: '.*\(....\)$$'部分。它将返回内部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

希望有帮助。

于 2016-02-10T16:38:35.247 回答
6

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

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

echo_progress.py

"""
Print makefile progress
"""

import argparse
import math
import sys

def main():
  parser = argparse.ArgumentParser(description=__doc__)
  parser.add_argument("--stepno", type=int, required=True)
  parser.add_argument("--nsteps", type=int, required=True)
  parser.add_argument("remainder", nargs=argparse.REMAINDER)
  args = parser.parse_args()

  nchars = int(math.log(args.nsteps, 10)) + 1
  fmt_str = "[{:Xd}/{:Xd}]({:6.2f}%)".replace("X", str(nchars))
  progress = 100 * args.stepno / args.nsteps
  sys.stdout.write(fmt_str.format(args.stepno, args.nsteps, progress))
  for item in args.remainder:
    sys.stdout.write(" ")
    sys.stdout.write(item)
  sys.stdout.write("\n")

if __name__ == "__main__":
  main()

和修改Makefile

_mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
I := $(patsubst %/,%,$(dir $(_mkfile_path)))

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 = python $(I)/echo_progress.py --stepno=$C --nsteps=$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 f.c g.c h.c i.c j.c k.c l.c m.c n.c o.c p.c q.c \
        r.c s.c t.c u.c v.c w.c x.c y.c z.c
    @$(ECHO) Linking $@
    @sleep 0.01
    @touch $@

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

endif

产量:

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

甚至可以打印带有 unicode 字符的精美进度条。

修改echo_progress.py

"""
Print makefile progress
"""

import argparse
import math
import sys

def get_progress_bar(numchars, fraction=None, percent=None):
  """
  Return a high resolution unicode progress bar
  """
  if percent is not None:
    fraction = percent / 100.0

  if fraction >= 1.0:
    return "█" * numchars

  blocks = [" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"]
  length_in_chars = fraction * numchars
  n_full = int(length_in_chars)
  i_partial = int(8 * (length_in_chars - n_full))
  n_empty = max(numchars - n_full - 1, 0)
  return ("█" * n_full) + blocks[i_partial] + (" " * n_empty)

def main():
  parser = argparse.ArgumentParser(description=__doc__)
  parser.add_argument("--stepno", type=int, required=True)
  parser.add_argument("--nsteps", type=int, required=True)
  parser.add_argument("remainder", nargs=argparse.REMAINDER)
  args = parser.parse_args()

  nchars = int(math.log(args.nsteps, 10)) + 1
  fmt_str = "\r[{:Xd}/{:Xd}]({:6.2f}%) ".replace("X", str(nchars))
  progress = 100 * args.stepno / args.nsteps
  sys.stdout.write(fmt_str.format(args.stepno, args.nsteps, progress))
  sys.stdout.write(get_progress_bar(20, percent=progress))
  remainder_str = " ".join(args.remainder)
  sys.stdout.write(" {:20s}".format(remainder_str[:20]))
  if args.stepno == args.nsteps:
    sys.stdout.write("\n")

if __name__ == "__main__":
  main()

这将导致这样的事情:

$ make clean && make
[12/28]( 42.86%) ███████▊             Compiling k.c

在进度和:

$ make clean && make
[28/28](100.00%) ████████████████████ All done 

完成后。

于 2019-07-16T15:43:06.143 回答
3

非常感谢@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
  • !=从 shell 命令分配
  • = 每次使用时评估变量
  • eval执行其参数而没有任何输出
  • expr允许进行算术计算

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

用法是一样的:

target:
    @$(ECHO) $@
于 2020-05-25T09:15:35.267 回答
2

好把戏!(-:

但对于分布在具有大量 makefile 的许多目录的不断增长的项目而言,它并不能真正扩展。

我更倾向于在您的项目中通过 [Mm]akefiles* 进行日志记录,并使用它来跟踪进度。

只是一个想法。顺便说一句,谢谢分享这个。

编辑:只是有一个想法。这在修改后的形式中可能很有用,以显示一个 throbber 以在一个长任务进行时显示进度,例如解压缩一个大型分发 tarball,而不是仅仅为 tar 命令指定 -v 选项。仍然有点糖衣,但也有点乐趣。(-:

干杯,

于 2009-01-16T18:41:02.047 回答