Home > Software engineering >  Makefile only recompile changed files
Makefile only recompile changed files

Time:03-16

I am trying to write a makefile but it does not do exactly what I want. I have the following files:

a.h, b.h, c.h, d.h, main.h

a_functions.c, b_functions.c, c_functions.c, d_functions.c, main.c

main.c includes main.h

And every x_functions.c includes x.h

I want make to recompile only the files that have been edited.

This is how my code looks like now:

CC = gcc
CFLAG = -Wall -Werror
OBJ = program1
CPPFLAGS  = -MD -MP
SRC = $(wildcard *.c)
 
all: $(SRC:%.c=%.o)
    $(CC) $(SRC) -o $(OBJ)

-include $(SRC:%.c=%.d)

What do I need to change to make it work? I have tried to find an answer on google but I can't find something that works (or something that I understand how to use)

CodePudding user response:

Your all target does not actually create a file called all. It creates a file $(OBJ). You should rewrite your Makefile to let Make know this. Also, CFLAG should be CFLAGS.

CC = gcc
CFLAGS = -Wall -Werror
OBJ = program1
CPPFLAGS  = -MD -MP
SRC = $(wildcard *.c)
 
.PHONY: all
all: $(OBJ)

$(OBJ): $(SRC:%.c=%.o)
    $(CC) $(SRC) -o $(OBJ)

-include $(SRC:%.c=%.d)

Instead of -MD you can also use -MMD. That option ignores system headers files, such as stdio.h, which are unlikely to change.

CodePudding user response:

I want make to recompile only the files that have been edited.

That is make's standard behavior, and one of the original motivations behind its invention. But in order for it to do that accurately, it needs correct and complete information about which targets are built from which sources, and how.

The basic reason why make recompiles all your sources on every run (some twice) is that the rule you provide doesn't give it a way to do differently:

  • The only explicit rule given is for a target named all. This is the default rule by virtue of being first, so all will be make's goal target if you do not name a different one on the command line.
  • The rule does not actually build a file named all, so it will always be considered out of date, and its recipe run.
  • The recipe explicitly compiles all the sources named in $(SRC).
  • Object files corresponding to the sources are named as prerequisites, so they will be updated if they are out of date, but the rule does not use them. As a result, some of the sources may be compiled twice -- once to create an (unused) object file, and once to create program1.

The minimum change needed to achieve your stated objective would be to make the rule for all a link rule instead of a compilation rule, by designating object files as the (only) inputs to the compiler. make's automatic $^ variable can help here:

all: $(SRC:%.c=%.o)
    $(CC) $^ -o $(OBJ)

$^ expands to a space-separated list of all the rule's prerequisites (so all the .o files in this case) with any duplicates removed. That will, therefore, use the previously-unused .o files instead of rebuilding the .c sources. The .o files will be updated (only) at need, according to make's built-in rule for building a .o file from a correspondingly-named .c file.

But that still leaves you with issues. The one you will notice first is that although make does not any longer recompile unchanged sources, it does re-link the program on every run, even if none of the sources changed. This is because the default (and only explicit) rule is for a target named all, but it does not actually build a target of that name. Its recipe will therefore be executed on every run, unless you happen to drop a new-enough, unrelated file named all in the source directory, in which case the program won't be rebuilt even when it ought to be.

To correct that, you should change the existing all rule to have the program being built as its actual target:

$(OBJ): $(SRC:%.c=%.o)
    $(CC) $^ -o $@

(Note that when it appears inside a rule's recipe, the automatic $@ variable expands to the name of the target for which the recipe was invoked.)

You could leave it at that, but if you want to have an all rule, too, then create one without a recipe, naming the actual program to be built as a prerequiste, and declare it phony:

.PHONY: all
all: $(OBJ)
# No recipe

That should precede the rule for building $(OBJ).

Additionally, note that make's built-in rules obtain various kinds of compiler flags from various standard variable names. Your CPPFLAGS is one of them (for GNU make) but CFLAG is not. You probably meant CFLAGS, instead.

Furthermore, as a matter of style, OBJ or OBJS is conventionally used to name object files, not programs. Often, makefiles for building a single program don't use a variable to designate its name at all. There is less convention for what to use if you do want a variable naming the overall program, but personally, I'd be inclined to use PROG or PROGRAM.

Overall, then:

CC = gcc
CFLAGS = -Wall -Werror
PROG = program1
CPPFLAGS  = -MD -MP
SRC = $(wildcard *.c)
OBJ = $(SRC:%.c=%.o)
 
.PHONY: all
all: $(PROG)

$(PROG): $(OBJ)
        $(CC) $(CFLAGS) $^ -o $@

-include $(SRC:%.c=%.d)

Finally, I would suggest naming your sources explicitly instead of using $(wildcard). This is more conventional, and it protects you against a few potential issues. I make this an addendum, however, because I have given up on persuading people to see past the allure of using wildcards, as your makefile does. I rest assured that those who use makefiles long enough will learn, in time. Or not.

  • Related