Makefile должен сработать в случае сбоя цели

#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. Большое спасибо. Ваше решение работает отлично и намного лучше и чище, чем то, как я подходил к проблеме.