Индикация выполнения make / makefile!

Посмотрите на этот make-файл, в нем есть какая-то примитивная индикация прогресса (могла быть индикатором выполнения).

Пожалуйста, дайте мне предложения / комментарии по этому поводу!



# 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)


Все предложения приветствуются!

Симпатичный трюк, но я не вижу, чтобы этого захотелось.

Paul Tomblin 16.01.2009 21:27

Я могу. На Gentoo это было бы хорошо. cmake имеет встроенный счетчик [файл x / n файлов]. Но было бы неплохо иметь индикатор выполнения без переполнения экрана каждой командной строкой.

Evi1M4chine 06.11.2017 04:34
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
27
2
18 012
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Хороший трюк! (-:

Но не очень масштабируемый для растущих проектов, которые распределены по множеству каталогов с большим количеством make-файлов.

Я был бы более склонен к тому, чтобы журналирование было пропущено через [Mm] akefiles * в вашем проекте и использовалось для отслеживания прогресса.

Просто мысль. Кстати, спасибо, что поделились этим.

Редактировать: Просто подумал. Это может быть полезно в модифицированной форме для отображения пульсирующего индикатора, показывающего прогресс, пока выполняется длинная задача, например, распаковка большого архива дистрибутива вместо простого указания опции -v для команды tar. Все еще немного сахара, но и весело. (-:

ваше здоровье,

Роб

Спасибо за комментарий, не могли бы вы объяснить, почему вы думаете, что это не масштабируется? Хорошо, код - это просто набросок, но MAX может содержать 64k x, если необходимо, и вычисление переменной T выполняется довольно быстро.

Giovanni Funchal 16.01.2009 21:51

Он не масштабируется, потому что он не будет работать, если вы используете make для нескольких каталогов, причем каждый подкаталог имеет свой собственный make-файл.

Scottie T 17.01.2009 00:44

@ ScottieT812, ура, это основная причина моего комментария! (-: Другой постоянно должен вычислять и обновлять количество x, необходимых по мере расширения проекта. Тем не менее, хороший трюк.

Rob Wells 17.01.2009 20:09

Роб, ты не понял мой сценарий. Он РАБОТАЕТ, если у вас есть make-файлы в нескольких каталогах (хотя я не рекомендую рекурсивный make), и x НЕ являются количеством целей, это просто верхняя граница (256, может быть больше). Автоматически определяется точное количество целей (см. Код $ T)

Giovanni Funchal 18.01.2009 18:14
Ответ принят как подходящий

Этот менее навязчивый и более интересный.

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

Чтобы заставить его переписать предыдущую строку, используйте это для строки ECHO: ECHO = echo -ne "\r `` expr " [\ `` expr $C '*' 100 / $T\ `` " : '.*\(....\)$$' `` %]" Он работает в bash, может не работать и с другими * nix'ами. Зависит от того, поддерживает ли echo-e и -n.

phyatt 10.02.2016 00:41

Обратите внимание на предыдущий комментарий: двойные обратные кавычки должны быть одинарными обратными кавычками, как в посте. Единственным добавлением был -ne после эха и \r после кавычек.

phyatt 10.02.2016 00:47

Это хорошо, но при использовании опции -j процентные значения не учитываются. Например [28%] Компиляция b.c [14%] Компиляция a.c ...

crististm 27.01.2017 13:41

Это небольшая модификация отличного отвечать @ 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

Я избавился от части : '.*\(....\)$$'. Он вернет последние 4 символа внутренней команды expr, но потерпит неудачу, если будет меньше 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

Надеюсь, это поможет.

Я использовал это в make-файле ядра Linux, и он подавляет вывод! сборка по-прежнему выполняется в фоновом режиме, но я ничего не вижу в STDOUT.

MOHAMMAD RASIM 01.09.2018 10:51

Вероятно, это \r в эхо ... Это держит его каждый раз на одной и той же строке, или -ne часть эха.

phyatt 01.09.2018 14:07

проблема сохраняется даже после их удаления, возможно, сценарии ядра KBUILD делают что-то особенное, чего этот код не покрывает?

MOHAMMAD RASIM 01.09.2018 17:18

На самом деле не было вопроса, поэтому это не просто отдельный ответ, а скорее расширение решение Джованни Фунчай. Этот вопрос является первым результатом Google для "GNU Make Progress", поэтому я пришел сюда, чтобы узнать, как это сделать.

Как указал Роб Уэллс, решение не работает для <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

Можно даже напечатать причудливый индикатор выполнения с символами Юникода.

Модифицированный 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 

по завершении.

Хороший ! Должен быть наверху, но я думаю, что у вашего второго кода на Python есть недостаток. Он использует символы, отличные от ASCII, но не имеет объявления кодировки. Вы должны добавить, например, # coding: utf-8 в верхней части скрипта. См. это. Тем не менее, спасибо так много :)

Parsa Mousavi 26.08.2020 14:59

Большое спасибо @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
  • != назначает из команды оболочки
  • = оценивает переменную каждый раз, когда она используется
  • eval выполняет свой аргумент без вывода
  • expr позволяет производить арифметические вычисления

(Не знаю, какой подход быстрее: вызвать оболочку с помощью expr или подсчитать x с помощью make.)

Использование такое же:

target:
    @$(ECHO) $@

Приятно видеть, что спустя 10 лет после моей первоначальной публикации это все еще привлекает внимание;)

Giovanni Funchal 26.05.2020 15:47

Другие вопросы по теме