AtilioA
AtilioA

Reputation: 440

Makefile to compile all .c files without needing to specify them

I'm trying to create a Makefile that causes all .c files to be compiled without the need to add filenames line per line inside the Makefile. I think this is fairly similar to makefiles - compile all c files at once. Here's what I want to do:

1 - Verify if all .c files from /src have their respective .o files from /obj. If the .o file does not exist, build it from the .c file;

2 - Compile main.exe from all .o and put it on /bin.

My Makefile:

BIN     = ./bin
OBJ     = ./obj
INCLUDE = ./include
SRC     = ./src

all:
    gcc -c "$(SRC)/Distances.c" -o "$(OBJ)/Distances.o"
    gcc -c "$(SRC)/HandleArrays.c" -o "$(OBJ)/HandleArrays.o"
    gcc -c "$(SRC)/HandleFiles.c" -o "$(OBJ)/HandleFiles.o"
    gcc -c "$(SRC)/Classifier.c" -o "$(OBJ)/Classifier.o"
    gcc -c main.c -o "$(OBJ)/main.o"
    gcc -o $(BIN)/main.exe $(OBJ)/*.o -lm

run:
    $(BIN)/main.exe

clean:
    del /F /Q "$(OBJ)" ".o"
    del /F /Q "$(BIN)" ".exe"

I guess I need to use something like $(SRC)/*.c, but I couldn't make it.

Now, about make clean, I don't know how to make it work cross-platform. I'm using del as I'm on Windows.

Upvotes: 7

Views: 23137

Answers (2)

Renaud Pacalet
Renaud Pacalet

Reputation: 29050

Note: this answer assumes that you are using GNU make. If it is not the case there are probably a few things to adapt. I will not answer your last question about cross-platform portability. First because it is a complex question, second because I do not have a Windows box and cannot do any tests with this OS. But if you know a portable way to detect the OS, look at the last note. Meanwhile, the following should work under Windows.

The most straightforward and standard way to use GNU make in your case is probably something like:

MKDIR   := md
RMDIR   := rd /S /Q
CC      := gcc
BIN     := ./bin
OBJ     := ./obj
INCLUDE := ./include
SRC     := ./src
SRCS    := $(wildcard $(SRC)/*.c)
OBJS    := $(patsubst $(SRC)/%.c,$(OBJ)/%.o,$(SRCS))
EXE     := $(BIN)/main.exe
CFLAGS  := -I$(INCLUDE)
LDLIBS  := -lm

.PHONY: all run clean

all: $(EXE)

$(EXE): $(OBJS) | $(BIN)
    $(CC) $(LDFLAGS) $^ -o $@ $(LDLIBS)

$(OBJ)/%.o: $(SRC)/%.c | $(OBJ)
    $(CC) $(CFLAGS) -c $< -o $@

$(BIN) $(OBJ):
    $(MKDIR) $@

run: $(EXE)
    $<

clean:
    $(RMDIR) $(OBJ) $(BIN)

Explanations:

  1. The wildcard make function is used to discover the list of C source files.
  2. The patsubst make function is used to transform C source file names into object file names.
  3. The .PHONY special target tells make that all its prerequisites are phony: they are not real files and must be considered by make even if a file with this name already exists by accident.
  4. $(OBJ)/%.o: $(SRC)/%.c | $(OBJ) is a pattern rule, a generic rule that works for all your similar object-building rules. This one tells make how to produce each ./obj/xxx.o object file by compiling the corresponding ./src/xxx.c C source file.
  5. The ... | $(OBJ) part of the pattern rule tells make that $(OBJ) is an order-only prerequisite. Make will build it if it does not exist already. Else it will not consider its last modification time to decide if the target must be rebuilt or not. Directories are almost always listed as order-only prerequisite because their last modification time is not relevant. Here, it is used to tell make that the $(OBJ) directory must be built before any object file can be built. Same for $(BIN) in the $(EXE): $(OBJS) | $(BIN) rule.
  6. $@, $< and $^ are 3 of the make automatic variables. In the recipes of a rule they expand respectively as the target, the first regular prerequisite and all regular (non order-only) prerequisites of the rule.
  7. CC, CFLAGS and LDLIBS are standard make implicit variables used respectively to define the C compiler, the C compiler options and the -lxxx linker options.
  8. The := variable assignment is preferable in this specific case over the = assignment for performance reasons. See the GNU make documentation for a detailed explanation.

Note: as you have an include directory I guess that you also have custom header files. They should be listed as prerequisites of the relevant object files such that make knows that an object file must be rebuilt if the header files it depends on change. You can do this by adding rules without a recipe anywhere after the pattern rule:

$(OBJ)/Distances.o: $(INCLUDE)/foo.h $(INCLUDE)/bar.h

If a header file is included by all C source files of your project, simply add:

$(OBJS): $(INCLUDE)/common.h

And if there is a kind of pattern for the header files (for instance if each $(SRC)/xxx.c includes $(INCLUDE)/xxx.h) you can also adapt the pattern rule to declare this kind of dependency:

$(OBJ)/%.o: $(SRC)/%.c $(INCLUDE)/%.h | $(OBJ)
    (CC) $(CFLAGS) -c $< -o $@

Note: if you know a way to set a make variable (e.g. OS) to the name of the current OS you can probably modify what precedes to make it portable using GNU make conditionals. For instance, you could replace the two first lines by:

OS := $(shell <the-command-that-returns-the-current-OS-name>)

ifeq ($(OS),Windows)
MKDIR   := md
RMDIR   := rd /S /Q
else ifeq ($(OS),GNU/Linux)
MKDIR   := mkdir -p
RMDIR   := rm -rf
else ifeq  ($(OS),pokemon)
MKDIR   := bulbasaur
RMDIR   := charmander
endif

Upvotes: 25

anand
anand

Reputation: 163

% wildcard pattern rule can help %.o:%c

Upvotes: -2

Related Questions