rrrrrrrrrrrrrrrr
rrrrrrrrrrrrrrrr

Reputation: 342

Makefile cutting out variables in for loop

I'm writing my first complex Makefile for a highly-modularized project.

I have various sub-directories, each one has its own Makefile which supports at least the all and the clean targets.

These sub-Makefiles work just fine, however I have a problem with the main Makefile, that should call all the sub-Makefiles automatically from the list contained in the variable COMPONENTS.

I tried with the following Makefile:

OUTFILE = diskimage.bin

export NASM = nasm

COMPONENTS = bootloader

.PHONY = all clean FORCE $(OUTFILE) $(COMPONENTS)

all: $(OUTFILE)

$(OUTFILE): $(COMPONENTS)
    ./make_image

$(COMPONENTS): FORCE
    for component in $(COMPONENTS); do \
        make -C $component; \
    done

FORCE:

clean:
    for component in $(COMPONENTS); do \
        make -C $component clean; \
    done

This results in the following error message:

for component in bootloader; do \
                make -C omponent; \
        done
make: *** omponent: No such file or directory. Stop.
make: *** [bootloader] Error 2

as if the $component expression was only parsed as $c. I don't understand why that happens and how to fix it.

Upvotes: 0

Views: 628

Answers (2)

bobbogo
bobbogo

Reputation: 15493

Several issues.

  • .PHONY should be written as a dependency, not a macro definition
  • Don't write shell loops, use make syntax instead
  • When you call make recursively, you must do it via the ${MAKE} macro invocation

Leading to

OUTFILE = diskimage.bin
export NASM = nasm
COMPONENTS = bootloader

.PHONY: all
all: ${OUTFILE}

.PHONY: ${OUTFILE}
${OUTFILE}: ${COMPONENTS}
    ./make_image

.PHONY: ${COMPONENTS}
${COMPONENTS}:
    ${MAKE} -C $@

The advantage of this formulation is that it is parallel make friendly. Always a test of a good Makefile. Here make -j5 all will cause make to keep 5 commands running at once, across all invocations of make. Nice if you have 4 CPUs.

What about clean? (Personally I hate clean targets—it's a sign of dodgy dependencies, and of unhygienic mixing of source and target folders.)

Just add -clean (say) to each of the component names, and repeat the pattern above.

CLEANS := $(addsuxffix -clean,${COMPONENTS})

.PHONY: clean
clean: ${CLEANS} ; @echo Clean succesful

.PHONY: ${CLEANS}
${CLEANS}: %-clean:
    ${MAKE} -C $* clean

These two sections can tidied up and combined into one if you feel so inclined.

Tip

Always run make with --warn (or --warn-undefined-variables to give it its full name) to catch inadvertent expansion of $c in things like $component.

Upvotes: 1

Beta
Beta

Reputation: 99144

Just double the dollar sign:

$(COMPONENTS): FORCE
    for component in $(COMPONENTS); do \
  make -C $$component; \
  done

The trouble is that with your makefile, Make expands $component before executing the rule. And since $c has no value (there is no such variable), it expands to nothing, leaving "omponent", which it passes to she shell, which complains that there's no such directory. (If you had written $(component), Make would have expanded it to nothing, since Make knows of no such variable, and then the shell would have complained that you were not specifying a directory at all.)

With the double dollar sign, Make expands $$component to $component, which it then passes to the shell, which interprets it as the loop variable, and everything proceeds as planned.

You really should have played around with a simple loop in a command, before attempting to do actual work with one.

Upvotes: 2

Related Questions