简体   繁体   中英

How do I change a makefile target into a function?

I am looking for a clean way to change a few target declarations I created in a makefile into a more functional type declaration in which I am able to pass variables and the output will remain consistent.

For example:

default: clean run1 run2

run1:
    for entity in $(FLIST_01); do \
        echo $entity ; \
    done

run2:
    for entity in $(FLIST_02); do \
         echo $entity ; \
    done

Ideally, I would like to remove the repetitive run target declarations and only have 1.

FLIST_01 = my_flist.txt
FLIST_02 = other.txt

default: clean run

run:
    $(run_func $(FLIST_01))
    $(run_func $(FLIST_02))

How do I create a custom function in make to do what run_func is supposed to be doing (the for loop reading of the file list variable passed to it?

UPDATE:

My attempt so far as been this:

run:
    runfunc() { \
        echo "test" \
        for entity in $1; do \
            echo $(entity); \
        done \
    }

    runfunc $(FLIST_01)
    runfunc $(FLIST_02)

But I get a syntax error on the do line: syntax error near unexpected token `do'

Usually, it is not easier to maintain shell functions inside Makefile. Main reasons:

  • Each shell '$' need to be escaped to '$$'
  • The $(...) construct has different meaning in shell vs Make.
  • Each line in the Makefile is executing by new shell instance, meaning that variables assignment, functions definitions, etc, are not shared between lines.

Consider few options: write shell helper script (recommended), use make user-defined functions or inline the function into the Make. For the specific case of the "echo", it might be possible to use one of the other approaches, see "Alternative Solution"

From personal experience, for any nontrivial functions, better to use shell helper script. They are much easier to develop and test.

Using Shell Helper Script

Write a small helper script "print-list.sh"

#! /bin/bash
echo "test" \
for entity ; do
    echo $entity
done

And then invoke it from the target

FLIST_01 = a.txt b.txt c.txt

t1:
        print-list.sh ${FLIST_01)

Inlining the shell function

As indicated above, embedding the shell function into the Makefile requires matching make rules about quoting, escapes, etc. It also require lot of effort debugging the script, as error messages are very cryptic in this setup.

Note that RUNFUNC must be included before every action lines that uses the command.

RUNFUNC = runfunc() { \
        echo "test" ; \
        for entity in $$*; do \
            echo $$entity; \
        done \
    } ;

FLIST_01 = a.txt b.txt c.txt

t2:
        $(RUNFUNC) runfunc ${FLIST_01)

Note that the function can be written as one liner, or using the 'define'/'endef' to simplify end-of-line escapes.

Using make user-defined functions

It is possible to create simple functions using make. This option require experience and time.

UFUNC = \
        echo "test" ; \
        for entity in $1; do \
            echo $$entity ; \
        done


FLIST_01 = a.txt b.txt c.txt

t3:
        $(call UFUNC, ${FLIST_01))

Alternative Solution

Last option is to use existing construct (assuming that the only goal of the function is to convert the space-seperate list of files to new-line separated

t4:
        echo ${FLIST_01} | tr " " "\n"

First off, unless you specify .ONESHELL , the commands in a target will be run in separate subshells. So your runfunc() declaration will run in a shell which declares the function, then exits. The next statement will run in a new shell which will know nothing about the function which was declared and then basically forgotten.

Secondly,

echo "test" \
for entity in $1;

will be expanded by make into

echo "test" for entity in ;

which obviously contains multiple errors. You mean

echo "test"; \
for entity in $$1;

to properly pass the dollar sign through to the shell. But on the whole, I would say your approach is flawed. You can refactor this to a make function like you originally hoped;

define run
   for entity in $(1); do \
       echo $$entity; \
   done
endef

Now you can call this like

run:
    $(call run,$(FLIST_01))
    $(call run,$(FLIST_02))

But this particular loop can quite easily be replaced with a single shell statement.

run:
    printf '%s\n' $(FLIST_01)
    printf '%s\n' $(FLIST_02)

Another twopenn'orth.

First off, we want to expand $FLIST_01 for target run1 , and FLIST_02 for target run2 . A clean way might be:

FLIST_run1 := a b c
FLIST_run2 := 1 2 3

.PHONY: run1 run2
run1 run2:
    for entity in ${FLIST_$@}; do echo $entity; done

For a slightly more obscure solution we notice that what you are trying to do can be described with make 's noddy pattern matching:

.PHONY: run1 run2
run%: run1 run2:
    for entity in ${FLIST_$*}; do echo $entity; done

Here we use a static pattern rule. In the recipe, $* expands to whatever matched the % in the pattern. This solution looks quite clean to my jaundiced eye.

I urge you to seek out idiomatic make rather than idiomatic shell. Your makefiles will nearly always thank you for it.

In the first instance, keep your two run targets rather than coalesce them into the same recipe.

.PHONY: run1 run2
run%: run1 run2:
    for entity in ${FLIST_$*}; do echo $entity; done

.PHONY: run
run: run1
run: run2
run: ; echo $@ Success

Here, when you make run for instance, make first carries out the recipe for run1 , then the recipe for run2 , and finally the recipe for run .

Is there any advantage to this? Sure. If you run make -j2 run then the commands for run1 and run2 will be run in parallel. The whole point of make really.

The final issue is to get rid of the shell loop, and to replace it with separate commands. That way make will check the exit code of the echo for you, and even run them in parallel if that is suitable. Sure, not helpful in this noddy example, but they could be compile commands for instance.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM