Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Makefile always recompiling a file

I am learning how to set up makefiles, and have ran into a problem. To demonstrate this I have created a simple "project" consisting of source files main.m and test.m.

I am trying to setup make to compile these files (only if somethings changed), and store the object files in somewhere else (here build/)

My Makefile:

OBJ = ./build

SOURCES=main.m test.m
OBJECTS=$(addprefix $(OBJ)/,$(SOURCES:.m=.o))
EXECUTABLE=test

all: $(EXECUTABLE)

$(EXECUTABLE): $(OBJECTS)
        gcc $(OBJECTS) -o $(EXECUTABLE)

$(OBJECTS): $(OBJ)/%.o: %.m build/
        gcc -c $< -o $@

build/:
        mkdir build

When I run it for the first time (with only the Makefile and the sources in the current directory) it does what I expect it to do:

gcc -c main.m -o build/main.o
gcc -c test.m -o build/test.o
gcc ./build/main.o ./build/test.o -o test

However if I run make again:

gcc -c main.m -o build/main.o
gcc ./build/main.o ./build/test.o -o test

What have I done wrong? Also noting any other errors in the Makefile is appreciated, as I am trying to learn to create "good" Makefiles.

EDIT:

What I spotted from make -d:

Finished prerequisites of target file `build/main.o'.
Prerequisite `main.m' is older than target `build/main.o'.
Prerequisite `build/' is older than target `build/main.o'.
No need to remake target `build/main.o'.

and

Finished prerequisites of target file `build/test.o'.
Prerequisite `test.m' is older than target `build/test.o'.
Prerequisite `build/' is newer than target `build/test.o'.
Must remake target `build/test.o'.
like image 659
varesa Avatar asked May 24 '13 20:05

varesa


1 Answers

Your make -d output shows that make thinks your build directory has been updated, and so that file needs to be rebuilt.

I guess that happens because some operation either on your build system's part or by something else in your filesystem is causing some timestamp on that directory to be updated.

You can fix the problem by making build an order-only prerequisite by adding a | to that rule:

$(OBJECTS): $(OBJ)/%.o: %.m | build

I deleted the / too, since it wasn't doing anything.

Since you asked, some other editorial notes:

  1. Add a clean target. Something like:

    clean:
        rm -rf $(EXECUTABLE) $(OBJ)
    
  2. You don't need the ./ when you set OBJ. Just OBJ = build is enough.

  3. You don't need the / on build as mentioned above. But that doesn't really matter, since you shouldn't be referring it to it anyway. Repalce build with $(OBJ) wherever you see it.

  4. mkdir will fail if the directory already exists. You probably should prefix that command with a -:

    $(OBJ):
        -mkdir $(OBJ)
    

    Note that I've done the replacement with $(OBJ) that I mentioned in #3 above.

  5. Auto-generation of dependencies is very helpful. Your project as shown isn't big enough to really need it, but it's easy enough to add, so why not. You'll need to do a couple of things. First, get the appropriate dependency file names:

    DEPFILES = $(addprefix $(OBJ)/,$(SOURCES:.m=.d))
    

    Then get the compiler to generate them by adding the -MMD flag:

    gcc -MMD -c $< -o $@
    

    Lastly, include them in your makefile if they're available, by adding a line at the end of your makefile:

    -include $(DEPFILES)
    
like image 145
Carl Norum Avatar answered Oct 06 '22 06:10

Carl Norum