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'.
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:
Add a clean
target. Something like:
clean:
rm -rf $(EXECUTABLE) $(OBJ)
You don't need the ./
when you set OBJ
. Just OBJ = build
is enough.
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.
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.
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With