Search code examples
makefilesubdirectory

How can my makefile include subdirectories?


(updated for clarity) (solution added at bottom)

I found a makefile online which builds all the cpp files in that directory and compiles them.

But I can't work out how I can include files inside a subdirectory.

Here's a breakdown of what happens:

  • I create the files test.cpp & test.hpp and place them inside the sub-directory '/gui' which is contained within my working directory, they contain the function testFunction().
  • Without including test.hpp, I type "make" into terminal and I receive the error:

:

g++    -c -o main.o main.cpp
main.cpp: In function 'int main(int, char**)':
main.cpp:6:2: error: 'testFunction' was not declared in this scope
  testFunction();
  ^~~~~~~~~~~~
make: *** [<builtin>: main.o] Error 1
  • If I include (#include "gui/test.hpp"), I then receive a different error:

:

g++    -c -o main.o main.cpp
g++  main.o -Wall  -o testfile
/usr/bin/ld: main.o: in function `main':
main.cpp:(.text+0x14): undefined reference to `testFunction()'
collect2: error: ld returned 1 exit status
make: *** [makefile:34: testfile] Error 1
  • But if I then add "-I/gui" or (at a guess) "-I./gui" to CFLAGS, I get the exact same error message.

Here's the makefile for reference:

TARGET = testfile
LIBS = 
CC = g++
CFLAGS = -g -Wall

.PHONY: default all clean

default: $(TARGET)
all: default

OBJECTS = $(patsubst %.cpp, %.o, $(wildcard *.cpp))
HEADERS = $(wildcard *.hpp)

%.o: %.c $(HEADERS)
    $(CC) $(CFLAGS) -c $< -o $@

.PRECIOUS: $(TARGET) $(OBJECTS)

$(TARGET): $(OBJECTS)
    $(CC) $(OBJECTS) -Wall $(LIBS) -o $@

clean:
    -rm -f *.o
    -rm -f $(TARGET)

Thanks in advance!


Updated makefile since accepted answer:

(Changes were to include directories, CC replaced with CXX, and %.c replaced with %.cpp)

TARGET = testfile
DIRS =
LDLIBS =

CXX = g++

CXXFLAGS= -g -Wall

# this ensures that if there is a file called default, all or clean, it will still be compiled
.PHONY: default all clean

default: $(TARGET)
all: default

# substitute '.cpp' with '.o' in any *.cpp 
OBJECTS = $(patsubst %.cpp, %.o, $(wildcard *.cpp $(addsuffix /*.cpp, $(DIRS))))
HEADERS = $(wildcard *.h)

# build the executable
%.o: %.cpp $(HEADERS)
    $(CXX) $(CXXFLAGS) -c $< -o $@
    
# if make is interupted, dont delete any object file
.PRECIOUS: $(TARGET) $(OBJECTS)

# build the objects
$(TARGET): $(OBJECTS)
    $(CXX) $(OBJECTS) -Wall $(LDLIBS) -o $@ 

clean:
    -rm -f *.o $(addsuffix /*.o, $(DIRS))
    -rm -f $(TARGET)

Solution

  • To understand what's happening here you have to look up the definitions of declaration versus definition in C++ (and other languages). You should definitely do that.

    A declaration (typically put into a header file) is like the address of your house. If someone wants to send you a letter, they need your address. If your main function wants to call another function like testFunction(), it needs the declaration of the function.

    The first error happens because you don't have the header file included, so the compiler doesn't have the declaration of the function you want to call, which means it won't compile your calling function.

    But for the letter to actually arrive, you need your actual house. The address is the declaration and your house is the definition... in this case the actual function implementation. That lives in test.cpp file. When you link your code together, the linker (in this scenario I guess the linker is like the postal service :p :) ) will try to link up the call to the definition.

    However, you can see that you are not compiling the test.cpp file nor are you linking the object file:

    g++  main.o -Wall  -o testfile
    

    here we see main.o, but not gui/test.o.

    Why not? This line:

    OBJECTS = $(patsubst %.cpp, %.o, $(wildcard *.cpp))
    

    Matches all *.cpp files and converts them into .o files. But *.cpp matches only files in the current directory, like main.cpp. If you want to put files in a different directory you have to tell make where they are; for example:

    OBJECTS = $(patsubst %.cpp, %.o, $(wildcard *.cpp gui/*.cpp))