Evandro Coan
Evandro Coan

Reputation: 9475

How define a makefile condition and reuse it in several build rules?

This could or not be a duplicate of Makefile Variable Assignment Executes Early

Is it possible to define a condition and reuse it later?

MAIN_FILE_PATH := main

define copy_resulting_pdf=
if [[ -f "${MAIN_FILE_PATH}" ]] \
then \
    cp "${MAIN_FILE_PATH}" "${MAIN_FILE_PATH}.pdf"; \
else \
    $(error Error: The PDF ${MAIN_FILE_PATH} was not generated!); \
fi
endef

all:
    echo doing other things.
    $(copy_resulting_pdf)


other:
    echo doing more other things.
    $(copy_resulting_pdf)

When run this, make does not even start to build the all rule. It just throws out:

Makefile:14: *** Error: The PDF main was not generated!.  Stop.

Upvotes: 1

Views: 1133

Answers (3)

bobbogo
bobbogo

Reputation: 15513

Just one note to add: writing bash control structures in a recipe is a bit of a code smell IMHO. Things invariably look better is you replace them with make idioms.

Another smell here is when you lie to make: Your makefile does not say that the recipe for all creates main.pdf for instance.

MAIN_FILE_PATH := main

.PHONY: other_things
other_things:
    blah-dy-blah…

${MAIN_FILE_PATH}.pdf: | other_things
${MAIN_FILE_PATH}.pdf: ${MAIN_FILE_PATH}
    cp $< $@

.PHONY: all
all: ${MAIN_FILE_PATH}.pdf
    echo $@ Success

So what happens when you ask make to make all?

  • all:
    • pre-requisite: make needs to build main.pdf
      • pre-requisite: make builds other_things
        • blah-dy-blah…runs
      • recipe: main is copied to main.pdf
    • recipe: all Success is printed

The existence of main is checked by make rather than bash. One good characteristic of this is that you can easily add another recipe if you have a programmatic way of creating main. Another is that the copy does not happen if main.pdf is already up-to-date.

Since you are no longer lying to make, this makefile is parallel safe, allowing you to use the -jn flag. Hello performance.

Upvotes: 1

Matt
Matt

Reputation: 15196

The problem with your code is that $(error ...) is a text substitution too. So any expansion of $(copy_resulting_pdf) also leads to the recursive expansion of all embedded variables and functions, including $(error ...). Normally, $(error ...) is guarded by make conditionals (such as ifeq), or another substitutions (such as $(if ...)). But in your case it was "guarded" by shell conditional, which means nothing to make.

But the following works just as expected:

MAIN_FILE_PATH := main

define copy_resulting_pdf
    echo Printing results...
    if [[ -f "${MAIN_FILE_PATH}" ]]; then \
        printf 'Copying PDF...\n'; \
        cp "${MAIN_FILE_PATH}" "${MAIN_FILE_PATH}.pdf"; \
    else \
        printf "Error: The PDF \"${MAIN_FILE_PATH}\" was not generated!\n"; \
        exit 1; \
    fi
endef

.PHONY: all other
all:
    @echo doing other things.
    @$(copy_resulting_pdf)

other:
    @echo doing more other things.
    @$(copy_resulting_pdf)

Note: you don't need eval and some fancy escapes here.

Upvotes: 2

Evandro Coan
Evandro Coan

Reputation: 9475

I managed to do it with this:

SHELL := /bin/bash
MAIN_FILE_PATH := main

define copy_resulting_pdf=
echo Printing results...
eval "if [[ -f "${MAIN_FILE_PATH}" ]]; then \
    printf 'Coping PDF...\\n'; \
    cp "${MAIN_FILE_PATH}" "${MAIN_FILE_PATH}.pdf"; \
else \
    printf \"Error: The PDF "${MAIN_FILE_PATH}" was not generated!\\n\"; \
    exit 1; \
fi"
endef

all:
    echo doing other things.
    $(copy_resulting_pdf)

Running it:

echo doing other things.
doing other things.
echo Printing results...
Printing results...
eval "if [[ -f "main" ]]; then printf 'Coping PDF...\\n'; cp "main" "main.pdf"; else printf \"Error: The PDF "main" was not generated!\\n\"; exit 1; fi"
Error: The PDF main was not generated!
make: *** [setup/makefile.mk:17: all] Error 1

References:

  1. How can I use Bash syntax in Makefile targets?
  2. How to use ifeq inside of a define in GNU Make?

Upvotes: -1

Related Questions