4
votes

I have a project that includes many source files in different folder locations. For some reason my Makefile can either do one of the following but not both at the same time (which is what I really want):-

1) Compile all files into a separate directory

2) Perform the compilation ONCE, gcc needs to be called once only as this significantly reduces the compilation time.

This is a code snippet that works to achieve option 1:-

INCLDDIRS := "The needed include directories"
CFLAGS = "some c flags"
C_SOURCE = "Many many source files in different directories"
C_SOURCE_NAMES = $(notdir $(C_SOURCE))
OBJECT_DIRECTORY = ObjDir
C_OBJECTS = $(addprefix $(OBJECT_DIRECTORY)/, $(C_SOURCE_NAMES:.c=.o) )

all: $(OBJECT_DIRECTORY) $(C_OBJECTS)

$(OBJECT_DIRECTORY):
    mkdir ObjDir

$(OBJECT_DIRECTORY)/%.o:%.c
    $(CC) $(CFLAGS) $(INCLDDIRS) -c -o $@ $<

For some reason the above compiles each c source file individually and generates an object file (i.e. gcc is called for all source files). Which is not what I want. However, at least all generated files are located in ObjDir

and this is the code snippet that works to achieve option 2:-

INCLDDIRS := "The needed iclude directories"
CFLAGS = "some c flags"
C_SOURCE = "Many many source files in different directories"
C_SOURCE_NAMES = $(notdir $(C_SOURCE))
OBJECT_DIRECTORY = ObjDir
C_OBJECTS = $(OBJECT_DIRECTORY)/*.o

all: $(OBJECT_DIRECTORY) $(C_OBJECTS)

$(OBJECT_DIRECTORY):
    mkdir ObjDir

$(C_OBJECTS): (C_SOURCE)
    $(CC) $(CFLAGS) $(INCLDDIRS) -c $(C_SOURCE)

For the above snippet, all files are compiled once (i.e. gcc is called only once) but the object files are generated at the same location as the Makefile and not into a separate directory. I do not want to mv the files after they are generated as this is not the cleaner solution.

My Question is: What do I have to do to my Makefile so that compilation is performed once and that the object files are generated into a separate directory?

1
I think that you can use separate Makefile, one in each directory and the use $(MAKE) from the "main" makefile to run the others.terence hill
The first makefile is correct but not what you want. The second is simply incorrect.Etan Reisner

1 Answers

3
votes

The makefile you want will look something like this.

INCLDDIRS := "The needed include directories"
CFLAGS = "some c flags"
C_SOURCE = "Many many source files in different directories"
C_SOURCE_NAMES = $(notdir $(C_SOURCE))
OBJECT_DIRECTORY = ObjDir
BINARY := your_binary

all: $(BINARY)

$(OBJECT_DIRECTORY):
        mkdir $@

$(OBJECT_DIRECTORY/$(BINARY): $(C_SOURCE) | $(OBJECT_DIRECTORY)
        $(CC) $(CFLAGS) $(INCLDDIRS) -o $@ $^

It uses an order-only prerequisite for the output directory (so make knows to create it first but not count it as causing a rebuild).

It lists the source files as the prerequisites of the output binary and uses them all on the compilation line.

The main problem with this makefile, and with your goal, is that a change to any source file will cause every source file to be recompiled from scratch. That's fairly inefficient as far as incremental work goes. (That's why the default idea is to use intermediate object files. You trade some from-clean speed off against incremental speed.)

The reason your second makefile didn't work correctly is that, in a clean directory, the C_OBJECTS variable has no value. Your wildcard $(OBJECT_DIRECTORY)/*.o matches nothing.

That said it was also incorrect in that it listed every source file as a prerequisite for every object file which isn't at all correct.