#bash #makefile #grep
#bash #makefile #grep
Вопрос:
У меня есть makefile следующего формата —
all: target_1 target_2 target_3
target_1:
TASK_A1 | tee ta.log
TASK_A2 | tee -a ta.log
target_2:
TASK_B | tee tb.log
target_3:
TASK_C | tee tc.log
TASK_A генерирует выходные данные журнала в этом формате —
<some lines of output>
Errors: 0, Warnings: 12
<some more lines of output>
Errors: 5, Warnings: 10
Когда я создаю все, в идеале makefile должен выйти из строя, если target_1 терпит неудачу, но он продолжает работать с target_2.
Одно из возможных решений, о котором я могу подумать, — это grep «Ошибки: 0» из файлов журнала в каждой цели, а затем проверить возвращаемое значение grep (и выйти, если grep возвращает ненулевое значение). Я не считаю это очень эффективным решением, поскольку мне нужно выполнить вышеуказанные шаги для каждой цели.
Есть ли какой-либо более эффективный и интеллектуальный способ, которым я могу решить эту проблему?
Ответ №1:
Больше ничего не зная об отдельных задачах, вы больше ничего не можете сделать, кроме обработки выходных данных для «Ошибок: 0». Однако вы можете применить принцип DRY следующим образом:
runtask = $(1) | tee -a $(2) | awk '{ print; if (/Errors: 0/) y=1; } END { if (y) { exit 0 } else { exit 1 } }'
all: target_1 target_2 target_3
target_1:
@$(call runtask, TASK_A1, ta.log)
@$(call runtask, TASK_A2, ta.log)
target_2:
@$(call runtask, TASK_B, tb.log)
target_3:
@$(call runtask, TASK_C, tc.log)
По крайней мере, таким образом вам не придется просматривать каждую строку, если вам нужно изменить шаблон поиска.
Комментарии:
1. idk для остальных, но в awk
{ print; if (/Errors: 0/) y=1; } END { if (y) { exit 0 } else { exit 1 } }
эквивалентно{print} /Errors: 0/{y=1} END{exit !y}
Ответ №2:
Итак, прежде всего, если target_2
зависит от target_1
, вы должны явно указать это в своем makefile:
target_2: target_1
В противном случае, если кто-то выполняет сборку с помощью -j
, то target_2
, скорее всего, будет выполняться независимо от того, target_1
удалось ли.
Далее вы хотите, чтобы make завершался сбоем, если один из целевых объектов выходит из строя. Make завершится, если возврат из строки рецепта будет false (ненулевым). Я предполагаю, что TASK_A1
и друзья возвращают false, если они генерируют ошибки (в противном случае остальная часть этого сообщения является спорной).
К сожалению, у вас есть канал, который усложняет ситуацию, а именно, возврат из рецепта будет возвращаемым значением из tee
, а не TASK_A1
. Смотрите ниже:
bash> false
bash> echo $?
1
bash> false | tee blah
bash> echo $?
0
В этом случае tee blah
возвращено значение true ( 0
), поэтому $?
(код возврата) равен 0. К счастью, вы можете использовать PIPESTATUS
для получения кода возврата из первой части канала.
bash> false | tee blah; [ ${PIPESTATUS[0]} -eq 0 ]
bash> echo $?
1
bash> true | tee blah; [ ${PIPESTATUS[0]} -eq 0 ]
bash> echo $?
0
Итак… В заключение, вы могли бы сделать что-то вроде:
all: target_3
target_1:
TASK_A1 | tee ta.log; [ $${PIPESTATUS[0]} -eq 0 ]
TASK_A2 | tee -a ta.log; [ $${PIPESTATUS[0]} -eq 0 ]
target_2: target1
TASK_B | tee tb.log; [ $${PIPESTATUS[0]} -eq 0 ]
target_3: target2
TASK_C | tee tc.log; [ $${PIPESTATUS[0]} -eq 0 ]
Как только make попадет в задачу, которая завершается с ошибкой, она завершится с ошибкой.
Обратите внимание, что в этом решении, если TASK_A1
сбой, то TASK_A2
не будет выполняться. Я не был уверен, что это то, что вы хотите. Если вы хотите, чтобы оба выполнялись независимо, вы могли бы изменить target1
, чтобы сделать:
target1:
( TASK_A1; TASK_A2 ) | tee ta.log; [ $${PIPESTATUS[0]} -eq 0 ]
Комментарии:
1. Большое спасибо. Ваше решение работает отлично и намного лучше и чище, чем то, как я подходил к проблеме.