How to define global shell functions in a Makefile?

∥☆過路亽.° 提交于 2019-12-09 08:02:23

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!