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

ecfdbz9o  于 2023-10-23  发布在  Shell
关注(0)|答案(8)|浏览(163)

我想定义一个shell函数

#!/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

为了清楚起见,我希望shell函数成为Makefile的一部分。最优雅的方式是什么?如果你能做到这一点,而不需要递归地调用make,那就更好了。

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

hs1ihplo1#

这个解决方案不依赖于外部临时文件,也不强迫您修改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 (,$(filter n,$(MAKEFLAGS)))
TESTTOOL=: TESTTOOL
endif

foo: bar
    ${TESTTOOL} foo baz

ifneq…endif块检查命令行上的-n标志,并将TESTTOOL的扩展设置为: TESTTOOL,这易于阅读且安全执行。
最好的解决方案可能是将shell函数转换为实际的程序,如果这对您来说是一个选项。

kmpatx3s

kmpatx3s2#

由于这个问题被标记为gnu-make,你可能会对Beta的解决方案感到满意,但我认为最好的解决方案是1)将函数重命名为test以外的任何东西(也避免像lscd这样的名称);为了便于讨论,我们假设您已将其重命名为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

并不是说foo定义的每一行都必须有行延续,而且$都被转义,以便传递给shell。

fhity93d

fhity93d3#

我不认为这有资格作为“优雅”,但它似乎做你想要的:

##
## --- 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

运行此命令将产生:

$ 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

这依赖于这样一个事实,即在defineendef之间定义的宏在实际使用之前根本不会被make解释,因此您可以直接使用shell语法,并且不需要以反斜杠结束每一行。当然,如果你调用shell-functions宏,它会爆炸。

7eumitmz

7eumitmz4#

为什么不在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$

测试结果:

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
sigwle7e

sigwle7e5#

TL;DR:

在你的makefile中:

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

长回答

我已经在一个非make的上下文中用bash做了类似的事情,它在这里工作。
我在bash中声明了我的shell函数,然后用以下命令导出它们:

export -f function-name

如果同一个shell作为子进程被调用,那么它们自然是可用的,在您的例子中,是从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_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

试试看:

$ make
something-else
something else

它在Makefile中有点丑,但在make -n中没有丑

ijnw1ujt

ijnw1ujt6#

这里真的是贫民窟,不过无所谓了我使用zsh,但我相信有bash和sh的等价物。

生成文件:

export ZDOTDIR := ./

SHELL := /usr/bin/env zsh

default:
    f

.zshenv,与Makefile相同的目录:

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

ZDOTmap变量使zsh在当前目录中查找dotfiles。然后你只需要把你想要的东西放在.zshenv里。

$ make
f
CHECK OUT THIS AWESOME FUNCTION!
lc8prwob

lc8prwob7#

有些东西告诉我,你最好过滤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)
sy5wg1nm

sy5wg1nm8#

并不是我提倡这种方法,但是对于Idelic's "elegant" answer,你可以使用这个trick只生成一次shell函数文件:

.functions.sh : $(lastword $(MAKEFILE_LIST))
    sed -n '/^define shell-functions/,/^endef/{//!p;}' $< > $@

ifeq ($(wildcard .functions.sh),)
include .functions.sh
endif
SHELL := /bin/bash
.SHELLFLAGS = --init-file .functions.sh -i -c

这样做的目的是,如果文件不存在,那么包含它,这将导致make运行与包含文件名匹配的目标,然后重新执行自己,然后跳过include,因为文件现在存在。这必须在默认规则之后,并且一旦创建,如果调用makefile更改,它将不会被重新创建。

相关问题