Как определить глобальные функции оболочки в Makefile?

Я хочу определить функцию оболочки

#!/bin/sh

test ()
{
  do_some_complicated_tests $1 $2;
  if something; then
    build_thisway $1 $2;
  else
    build_otherway $1 $2;
  fi
}

таким образом, что я могу использовать его в каждом правиле моего файла Makefile, например:

foo: bar
  test foo baz

Чтобы быть ясным, я хочу, чтобы функция оболочки была частью Makefile. Каков самый элегантный способ сделать это? Бонусные баллы, если вы можете сделать это, не вызывая make рекурсивно.


Фон: Моя фактическая проблема заключается в том, что make -n производит очень длинный и нечитаемый вывод. Каждое правило использует почти ту же последовательность нечитаемых команд оболочки, и существует много правил. Вышеприведенное решение сделает вывод make -n более полезным.

Ответ 1

Это решение не полагается на внешний временный файл и не заставляет вас возиться с переменной SHELL.

TESTTOOL=sh -c '\
  do_some_complicated_tests $$1 $$2; \
  if something; then
    build_thisway $$1 $$2;
  else
    build_otherway $$1 $$2;
  fi' TESTTOOL

ifneq (,$(findstring n,$(MAKEFLAGS)))
TESTTOOL=: TESTTOOL
endif

foo: bar
    ${TESTTOOL} foo baz

Блок ifneq…endif проверяет флаг -n в командной строке и устанавливает расширение TESTTOOL на : TESTTOOL, которое легко читать и безопасно выполнять.

Лучшее решение может заключаться в том, чтобы превратить функцию оболочки в настоящую программу, если это вариант для вас.

Ответ 2

Поскольку вопрос помечен gnu-make, вы можете быть довольны решением Beta, но я считаю, что лучшим решением является 1) переименовать функцию, отличную от test (избегайте имен типа ls и cd также); для обсуждения предположим, что вы переименовали его foo, 2) просто напишите script с именем foo и вызовите его из Makefile. Если вы действительно хотите определить функцию оболочки в Makefile, просто определите ее для каждого правила:

F = foo() { \
  do_some_complicated_tests $$1 $$2; \
  if something; then \
    build_thisway $$1 $$2; \
  else \
    build_otherway $$1 $$2; \
  fi \
}

all: bar
  @$(F); foo baz bar

Не нужно, чтобы в каждой строке определения foo были продолжены строки, а $ - все экранированные, чтобы они передавались в оболочку.

Ответ 3

Что-то подсказывает, что вам лучше фильтровать вывод make -n, но то, что вы просите, возможно:

define test
  @echo do some tests with $(1) and $(2); \
  SOMETHING=$(1)_3 ; \
  if [ $$SOMETHING == foo_3 ]; then \
    echo build this way $(1) $(2); \
  else \
    echo build another way $(1) $(2) ; \
  fi
endef

someTarget:
    $(call test,foo,bar)

someOtherTarget:
    $(call test,baz,quartz)

Ответ 4

Я не думаю, что это считается "элегантным", но, похоже, делает то, что вы хотите:

##
## --- Start of ugly hack
##

THIS_FILE := $(lastword $(MAKEFILE_LIST))

define shell-functions
: BEGIN
  # Shell syntax here
  f()
  {
    echo "Here you define your shell function. This is f(): [email protected]"
  }

  g()
  {
    echo "Another shell function. This is g(): [email protected]"
  }
: END
endef

# Generate the file with function declarations for the shell
$(shell sed -n '/^: BEGIN/,/^: END/p' $(THIS_FILE) > .functions.sh)

# The -i is necessary to use --init-file
SHELL := /bin/bash --init-file .functions.sh -i

##
## -- End of ugly hack
##

all:
    @f 1 2 3 4
    @g a b c d

Запуск этого процесса:

$ make -f hack.mk
Here you define your shell function. This if f(): 1 2 3 4
Another shell function. This is g(): a b c d

Запуск с помощью -n вызывает:

$ make -f hack.mk -n
f 1 2 3 4
g a b c d

Это зависит от того, что макросы, определенные между define и endef, вообще не интерпретируются make до тех пор, пока они фактически не используются, поэтому вы можете напрямую использовать синтаксис оболочки, и вам не нужно заканчивать каждый линии с обратной косой чертой. Конечно, он будет бомбить, если вы вызовете макрос shell-functions.

Ответ 5

Это действительно гетто, но что угодно. Я использую zsh, но я уверен, что существуют эквиваленты bash и sh.

Makefile:

export ZDOTDIR := ./

SHELL := /usr/bin/env zsh

default:
    f

.zshenv, тот же каталог, что и Makefile:

f () { echo 'CHECK OUT THIS AWESOME FUNCTION!' }

Переменная ZDOTDIR делает zsh в текущем каталоге для dotfiles. Затем вы просто вставляете то, что хотите, в .zshenv.

$ make
f
CHECK OUT THIS AWESOME FUNCTION!

Ответ 6

TL; ДР:

В вашем make файле:

SHELL=bash
BASH_FUNC_command%%=() { ... }
export BASH_FUNC_command%%

Длинный ответ

Я сделал что-то подобное с bash в неконкретизируемом контексте, который работает здесь.

Я объявил свои функции оболочки в bash, а затем экспортировал их с помощью:

export -f function-name

Затем они естественно доступны, если одна и та же оболочка вызывается как подпроцесс, в вашем случае, из make.

Пример:

$ # define the function
$ something() { echo do something here ; }
$ export -f something

$ # the Makefile
$ cat > Makefile <<END
SHELL=bash
all: ; something
END
$

Попробуйте

$ make
something
do something here
$

Как это выглядит в среде?

$ env | grep something
BASH_FUNC_something%%=() { echo do something here

Итак, для вас будет вопрос, как установить переменные среды из Makefile. Шаблон выглядит как BASH_FUNC_ имя-функции %% =() { function-body}

Непосредственно в make

Итак, как это работает для вас? Попробуйте этот makefile

SHELL=bash
define BASH_FUNC_something-else%%
() {
  echo something else
}
endef
export BASH_FUNC_something-else%%

all: ; something-else

и попробуйте:

$ make
something-else
something else

Это немного уродливо в Makefile, но не представляет уродства для make -n