Лучший способ обработки труб и их статус выхода в make файле

Если команда make не работает, например gcc, она завершает работу...

gcc
gcc: fatal error: no input files
compilation terminated.
make: *** [main.o] Error 4

Однако, если у меня есть труба, берется статус выхода последней команды в трубе. В качестве примера gcc | cat не сбой, поскольку cat преуспевает.

Я знаю, что коды выхода для всего канала хранятся в массиве PIPESTATUS, и я могу получить код ошибки 4 с помощью ${PIPESTATUS[0]}. Как я должен структурировать мой make файл для обработки команды с каналами и выхода из строя как обычно?


Как и в комментариях, еще один пример: gcc | grep something. Здесь я предполагаю, что наиболее желаемое поведение по-прежнему остается для gcc и только gcc, чтобы вызвать сбой, а не grep, если он ничего не нашел.

Ответ 1

Вы можете сказать make использовать bash вместо sh и получить bash для установки set -o pipefail, чтобы он выходил с первым сбоем в конвейере.

В GNU Make 3,81 (и предположительно ранее, хотя я точно не знаю) вы должны сделать это с помощью SHELL = /bin/bash -o pipefail.

В GNU Make 3.82 (и новее) вы сможете сделать это с помощью SHELL = /bin/bash и .SHELLFLAGS = -o pipefail -c (хотя я не знаю, нужно ли добавлять -c в конец, как это необходимо, или если make будет добавьте это для вас, даже если вы укажете .SHELLFLAGS.

На странице bash man:

Возвратный статус конвейера - это статус выхода последнего, если параметр pipefail не включен. Если enabled, статус возврата конвейера - это значение последнего (самый правый) для выхода с ненулевым статусом или ноль, если все команды успешно завершены. Если зарезервированное слово! предшествует конвейер, статус выхода этого конвейера является логическим отрицанием статус выхода, как описано выше. Оболочка ждет всех команд в конвейере для завершения, прежде чем возвращать значение.

Ответ 2

Я бы пошел за pipefail. Но если вы действительно не хотите (или если вы хотите сбой только при первом процессе - не в случае отказа от остальной части трубы):

SHELL=bash

all:
        gcc | cat ; exit "$${PIPESTATUS[0]}"

Единственное преимущество, по сравнению с самим ответом @jozxyqk, заключается в том, что вы не теряете код статуса выхода.

Ответ 3

Просто добавьте в начало своей makefile команду:

SHELL=/bin/bash -o pipefail

Теперь вы можете, например, сгенерировать файл errors.err из объектов (1-е правило), не беспокоясь, что он будет перезаписан исполняемым файлом (2-е правило).

%.o : %.c
    gcc $(CFLAGS) $(CPPFLAGS) $^ -o [email protected] 2>&1 | tee errors.err

%.x : %.o $(OBJECTS)
    gcc $(LDLIBS) $^ -o [email protected] 2>&1 | tee errors.err

Без него make не получить ошибок из правила 1 и запустить правило 2, перезаписав его. У вас будет только одна строка в errors.err, указав, что для запуска нет

gcc: error: program.o: No such file or directory

Ответ 4

Разумным и переносимым подходом является реорганизация ваших заданий сборки для использования файлов вместо труб. Например:

foo:
    gcc >[email protected]
    grep success [email protected]
    cat [email protected]
    rm [email protected]

Удаление файла журнала после печати, очевидно, не требуется; это всего лишь общий шаблон. Говядина - это перенаправление для замены трубопровода. Вы даже можете реорганизовать его на несколько рецептов:

foo: foo.tmp foo.log
    grep success [email protected]
    mv $< [email protected]
%.tmp %.log:
    gcc -o $*.tmp >$*.log

Правильная очистка временных артефактов и, как правило, управление ими является очевидным недостатком такого подхода.