7

I have a set of directories, some of which contain makefiles, and some of the makefiles have clean targets. In the parent directory, I have a simple script:

#!/bin/bash

for f in *; do
        if [[ -d $f && -f $f/makefile ]]; then
                echo "Making clean in $f..."
                make -f $f/makefile clean
        fi
done

This does a weird thing when it hits a directory with a makefile without a (defined) "clean" target. For example, given two directories, one and two, containing;

one/makefile

clean:
        -rm *.x

two/makefile

clean:

In the second case "clean" is present without directives, so if you ran "make clean" in two you'd get:

make: Nothing to be done for `clean'.

Versus if there were no "clean":

make: *** No rule to make target `clean'.  Stop.

However, for the problem I'm about to describe, the result is the same whether the target is present but undefined or just not present. Running clean.sh from the parent directory;

Making clean in one...
rm *.x
rm: cannot remove `*.x': No such file or directory
make: [clean] Error 1 (ignored)

So one did not need cleaning. No big deal, this is as expected. But then:

Making clean in two...
cat clean.sh >clean 
chmod a+x clean

Why the cat clean.sh>clean etc.? Note I did create a minimal example exactly as shown above -- there are no other files or directories around (just clean.sh, directories one & two, and the very minimal makefiles). But after clean.sh runs, make has copied clean.sh > clean and made it executable. If I then run clean.sh again:

Making clean in one...
make: `clean' is up to date.
Making clean in two...
make: `clean' is up to date.
Press any key to continue...

Something even weirder, because now it is not using the specified makefiles at all -- it's using some "up to date" mystery target.

I've noticed a perhaps related phenomenon: if remove one of the test clauses in clean.sh like this:

#       if [[ -d $f && -f $f/makefile ]]; then
        if [[ -d $f ]]; then

And create a directory three with no makefile, part of the output includes:

Making clean in three...
make: three/makefile: No such file or directory
make: *** No rule to make target `three/makefile'.  Stop.

That there is no such file or directory I understand, but why does make then go on to look for a target with that name? The man page seems pretty straightforward:

-f file, --file=file, --makefile=FILE

  Use file as a makefile.

1 Answer 1

9

That behaviour is not a bug. It is a feature. A feature and a possible user error to be precise.

The feature in question is one of the implicit rules of Make. In your case the implicit rule to "build" *.sh files. The user error, your error, is not changing the working directory before invoking the makefile in the subdirectories.


TL; DR: to fix this you can do one or more of the following:

  1. Fix the shell script to change the working directory:

    #!/bin/bash
    
    for f in *; do
            if [[ -d $f && -f $f/makefile ]]; then
                    echo "Making clean in $f..."
                    (cd $f; make clean)
            fi
    done
    
  2. Make the empty rules explicit:

    clean: ;
    
  3. Make the clean targets phony:

    .PHONY: clean
    

Detailed explanation:

Make has a bunch of implicit rules. This allows one to invoke make on simple projects without even writing a makefile.

Try this for a demonstration:

  1. create an empty directory and change in to the directory
  2. create a file named clean.sh.
  3. run make clean

Output:

$ make clean
cat clean.sh >clean 
chmod a+x clean

BAM! That is the power of implict rules of make. See the make manual about implicit rules for more information.


I will try to answer the remaining open question:

Why does it not invoke the implicit rule for the first makefile? Because you overwrote the implicit rule with your explicit clean rule.

Why does the clean rule in the second makefile not overwrite the implicit rule? Because it had no recipe. Rules with no recipe do not overwrite the implicit rules, instead they just append prerequisites. See the make manual about multiple rules for more information. See also the make manual about rules with explicit empty recipes.

Why is it an error to not change the working directory before invoking a makefile in a subdirectory? Because make does not change the working directory. Make will work in the inherited working directory. Well technically this is not necessarily an error, but most of the time it is. Do you want the makefiles in the subdirectories to work in the subdirectories? Or do you want them to work in the parent directory?

Why does make ignore the explicit clean rule from the first makefile in the second invocation of clean.sh? Because now the target file clean already exists. Since the rule clean has no prerequisites there is no need to rebuild the target. See the make manual about phony targets which describes exactly this problem.

Why does make search for the target three/makefile in the third invocation? Because make always tries to remake the makefiles before doing anything else. This is especially true if the makefile is explicitly requested using -f but it does not exists. See the make manual about remaking makefiles for more information.

1
  • I think this answer would be improved by adding that for each respective version of GNU make (yes, other flavors exist and are in use) make -npf /dev/null (or gmake depending on the system) can be used to dump the database of implicit rules for that exact version. Has helped me oftentimes. Commented May 25, 2014 at 21:45

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .