问题
I want to define a shell function
#!/bin/sh
test ()
{
do_some_complicated_tests $1 $2;
if something; then
build_thisway $1 $2;
else
build_otherway $1 $2;
fi
}
in such a way that I can use it in every rule of my Makefile, such as:
foo: bar
test foo baz
To be clear, I want the shell function to be part of the Makefile. What is the most elegant way to do this? Bonus points if you can do it without calling make recursively.
Background:
My actual problem is that make -n
produces a very long and unreadable output. Each rule uses almost the same sequence of unreadable shell commands, and there are many rules. The above solution would make the output of make -n
more useful.
回答1:
This solution does not rely on an external temporary file and does not force you to tinker with the SHELL
variable.
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
The ifneq…endif
block checks for the -n
flag on the command line and sets the expansion of TESTTOOL
to : TESTTOOL
which is easy to read and safe to execute.
The best solution could be to turn the shell function into an actual program if this is an option for you.
回答2:
Since the question is tagged gnu-make
, you might be happy with Beta's solution, but I think the best solution is to 1) rename the function anything other than test
(avoid names like ls
and cd
as well); for the purpose of discussion let's assume you've renamed it foo
, 2) simply write a script named foo
and invoke it from the Makefile. If you really want to define a shell function in the Makefile, just define it for each rule:
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
Not that you must have line continuations on each line of the definition of foo
, and the $
are all escaped so that they are passed to the shell.
回答3:
I don't think this qualifies as "elegant", but it seems to do what you want:
##
## --- 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
Running this produces:
$ 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
Running it with -n
produces:
$ make -f hack.mk -n
f 1 2 3 4
g a b c d
This relies on the fact that macros defined between define
and endef
are not interpreted by make
at all until they are actually used, so you can use shell syntax directly and you don't need to end each line with a backslash. Of course, it will bomb if you call the shell-functions
macro.
回答4:
Something tells me you'd be better off filtering the output of make -n
, but what you ask is possible:
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)
回答5:
This is really ghetto, but whatever. I use zsh, but I'm sure there are bash and sh equivalents.
Makefile:
export ZDOTDIR := ./
SHELL := /usr/bin/env zsh
default:
f
.zshenv, same directory as Makefile:
f () { echo 'CHECK OUT THIS AWESOME FUNCTION!' }
The ZDOTDIR variable makes zsh look in the current directory for dotfiles. Then you just stick what you want in .zshenv
.
$ make
f
CHECK OUT THIS AWESOME FUNCTION!
回答6:
TL;DR:
In your makefile:
SHELL=bash
BASH_FUNC_command%%=() { ... }
export BASH_FUNC_command%%
Long answer
I've done something similar with bash in a non-make context which works here.
I declared my shell functions in bash and then exported them with:
export -f function-name
They are then naturally available if the same shell is invoked as a sub-process, in your case, from make.
Example:
$ # define the function
$ something() { echo do something here ; }
$ export -f something
$ # the Makefile
$ cat > Makefile <<END
SHELL=bash
all: ; something
END
$
Try it out
$ make
something
do something here
$
How does this look in the environment?
$ env | grep something
BASH_FUNC_something%%=() { echo do something here
So the question for you would be how to set the environment variables from within the Makefile. The pattern seems to be BASH_FUNC_function-name%%=() { function-body }
Directly within make
So how does this work for you? Try this makefile
SHELL=bash
define BASH_FUNC_something-else%%
() {
echo something else
}
endef
export BASH_FUNC_something-else%%
all: ; something-else
and try it:
$ make
something-else
something else
It's a bit ugly in the Makefile but presents no ugliness for make -n
回答7:
Why not using .ONESHELL in Makefile and define a Makefile function like bash function:
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$
Result:
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
来源:https://stackoverflow.com/questions/12774787/how-to-define-global-shell-functions-in-a-makefile