makefile foreach with conditional

54 Views Asked by At

New here to Makefile markup language, but I'm trying to create a target to allow optional argument. For the given argument, we'll expect it can be single, or multiple inputs separated by space. If the argument is given, I want to iterate through the argument and run a testing function for each argument.

It runs if the argument is a single input, but fails if multiple inputs are provided.

run-test:
    ifdef tests
            $(foreach test_file, $(tests), $(if $(wildcard $(addprefix tests/python/test_, $(addsuffix .py, ${test_file}))), export env=preprod && pytest tests/python/test_${test_file}.py, $(info ${test_file} does not exist.)))
    endif

if i invoke make run-test tests="foo" it runs fine, but if i do run-test tests="foo bar" it seems like the test cli commands to export and run pytest command get chained together and it doesnt work.

1

There are 1 best solutions below

4
MadScientist On

The most direct answer to your question is that make's functions are simply text manipulation operations. They don't run commands or anything.

So if you have something like this:

FILES = foo.py bar.py baz.py

$(info $(foreach F,$(FILES), python $F))

you'll see the output is:

python foo.py python bar.py python baz.py

and if you put that string into your shell prompt to run it, you'll get a single python interpreter with arguments foo.py python bar.py python baz.py. If you want to run multiple commands you have to ensure the text generated by the functions are valid shell commands. In this case, that might mean adding a semicolon between them:

FILES = foo.py bar.py baz.py

$(info $(foreach F,$(FILES), python $F ;))

you'll see the output is:

python foo.py ; python bar.py ; python baz.py ;

Now it will work, but the problem here is that if any one of them (except the last one) fails it will lose the error code. Maybe you don't care about that.

But stepping back, the entire way you're going about this is not really "make-like". Whenever you have a single recipe that's doing one thing for every file in a list, that's not really how make was designed. Make was designed to have one rule that does one thing. If I were you I would rewrite your makefile like this:

run-test: $(tests)

.PHONY: $(tests)
$(tests): % : tests/python/test_%.py
        env=preprod pytest $<

This uses a static pattern rule to define a separate rule for every test, that will run that test.