如何在Makefile中定义全局shell函数?

Hol*_*ger 17 shell makefile sh gnu-make

我想定义一个shell函数

#!/bin/sh

test ()
{
  do_some_complicated_tests $1 $2;
  if something; then
    build_thisway $1 $2;
  else
    build_otherway $1 $2;
  fi
}
Run Code Online (Sandbox Code Playgroud)

以这种方式,我可以在我的Makefile的每个规则中使用它,例如:

foo: bar
  test foo baz
Run Code Online (Sandbox Code Playgroud)

要清楚,我希望shell函数成为Makefile的一部分.这样做最优雅的方法是什么?如果您可以在不递归调用make的情况下执行此操作,则可获得积分.


背景:我的实际问题是make -n产生一个非常长且不可读的输出.每个规则使用几乎相同的不可读shell命令序列,并且有许多规则.上述解决方案将使输出make -n更有用.

Mic*_*ald 12

此解决方案不依赖于外部临时文件,也不会强制您修改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
Run Code Online (Sandbox Code Playgroud)

ifneq…endif块检查所述-n命令行上的标志,并设置的扩张TESTTOOL: TESTTOOL其易于阅读和安全执行.

如果这是一个选项,最好的解决方案是将shell函数转换为实际程序.


sun*_*ogo 7

为什么不在Makefile 中使用.ONESHELL并定义像 bash 函数这样的 Makefile 函数:

jeromesun@km:~/workshop/hello.test$ tree
.
??? Makefile
??? mkfiles
    ??? func_test.mk

1 directory, 2 files
jeromesun@km:~/workshop/hello.test$ cat Makefile 
.ONESHELL:

-include mkfiles/*.mk

test:
        @$(call func_test, one, two)
jeromesun@km:~/workshop/hello.test$ cat mkfiles/func_test.mk 
.ONESHELL:

define func_test
    echo "func_test parameters: 0:$0, 1:$1, 2:$2"
    if [ ! -d abc ]; then
        mkdir abc
        echo "abc not found"
    else
       echo "abc found"
    fi
endef
jeromesun@km:~/workshop/hello.test$ 
Run Code Online (Sandbox Code Playgroud)

结果:

jeromesun@km:~/workshop/hello.test$ make test 
func_test parameters: 0:func_test, 1: one, 2: two
abc not found
jeromesun@km:~/workshop/hello.test$ make test 
func_test parameters: 0:func_test, 1: one, 2: two
abc found
Run Code Online (Sandbox Code Playgroud)


Wil*_*ell 6

由于这个问题被标记gnu-make,你可能会很乐意与Beta版的解决方案,但我认为最好的办法是:1)重命名以外的任何功能test(避免这样的名字ls,并cd为好); 为了讨论的目的,让我们假设你已经重命名了它foo,2)只需编写一个名为的脚本foo并从Makefile中调用它.如果您真的想在Makefile中定义shell函数,只需为每个规则定义它:

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
Run Code Online (Sandbox Code Playgroud)

并不是说你必须在定义的每一行都有行继续foo,并且$都被转义以便它们被传递给shell.


Sam*_*ott 6

长话短说:

在你的 makefile 中:

SHELL=bash
BASH_FUNC_command%%=() { ... }
export BASH_FUNC_command%%
Run Code Online (Sandbox Code Playgroud)

长答案

我已经在非 make 上下文中用 bash 做了类似的事情,在这里工作。

我在 bash 中声明了我的 shell 函数,然后使用以下命令导出它们:

export -f function-name
Run Code Online (Sandbox Code Playgroud)

如果同一个 shell 作为子进程(在您的情况下是从 make 调用),那么它们自然可用。

例子:

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

$ # the Makefile
$ cat > Makefile <<END
SHELL=bash
all: ; something
END
$
Run Code Online (Sandbox Code Playgroud)

试试看

$ make
something
do something here
$
Run Code Online (Sandbox Code Playgroud)

这在环境中看起来怎么样?

$ env | grep something
BASH_FUNC_something%%=() { echo do something here
Run Code Online (Sandbox Code Playgroud)

因此,您面临的问题是如何从 Makefile 中设置环境变量。该模式似乎是 BASH_FUNC_ function-name %%=() { function-body }

直接在 make 内

那么这对你有什么作用呢?试试这个 makefile

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

all: ; something-else
Run Code Online (Sandbox Code Playgroud)

并尝试一下:

$ make
something-else
something else
Run Code Online (Sandbox Code Playgroud)

Makefile 中有点难看,但make -n并不难看


Ide*_*lic 5

我认为这不算“优雅”,但似乎可以满足您的要求:

##
## --- 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(): $@"
  }

  g()
  {
    echo "Another shell function. This is g(): $@"
  }
: 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
Run Code Online (Sandbox Code Playgroud)

运行此命令将产生:

$ 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
Run Code Online (Sandbox Code Playgroud)

运行它-n会产生:

$ make -f hack.mk -n
f 1 2 3 4
g a b c d
Run Code Online (Sandbox Code Playgroud)

这依赖于这样的事实,在define和之间定义的宏在实际使用之前根本endef不会被解释make,因此您可以直接使用shell语法,而无需在每行后面加反斜杠。当然,如果您调用shell-functions宏,它将炸弹。

  • 因为您可能希望在同一命令中多次使用`f.sh`。使用`--init-file`,shell只加载一次函数。无论如何,除非f.sh很大,否则差异仅是很小的。我的重点是清洁和“优雅”,而不是速度。顺便说一句,对我来说,“丑陋”的部分是.sh文件的生成,而不是`SHELL:= ...`。 (2认同)

Cas*_*mor 5

这真的是贫民窟,但无论如何。我使用 zsh,但我确定有 bash 和 sh 等价物。

生成文件:

export ZDOTDIR := ./

SHELL := /usr/bin/env zsh

default:
    f
Run Code Online (Sandbox Code Playgroud)

.zshenv,与 Makefile 相同的目录:

f () { echo 'CHECK OUT THIS AWESOME FUNCTION!' }
Run Code Online (Sandbox Code Playgroud)

ZDOTDIR 变量使 zsh 在当前目录中查找点文件。然后你只要坚持你想要的.zshenv

$ make
f
CHECK OUT THIS AWESOME FUNCTION!
Run Code Online (Sandbox Code Playgroud)