Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write a Makefile with separate source and header directories?

Tags:

Following this tutorial...

I have 2 source files and 1 header file. I want to have them in separate directories like in the tutorial.

So I set this project up:

.
├── include
│   └── hellomake.h
├── Makefile
└── src
    ├── hellofunc.c
    └── hellomake.c

Makefile:

IDIR =../include
CC=gcc
CFLAGS=-I$(IDIR)

ODIR=obj
LDIR =../lib

_DEPS = hellomake.h
DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS))

_OBJ = hellomake.o hellofunc.o 
OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ))

$(ODIR)/%.o: %.c $(DEPS)
    $(CC) -c -o $@ $< $(CFLAGS)

hellomake: $(OBJ)
    gcc -o $@ $^ $(CFLAGS)

.PHONY: clean

clean:
    rm -f $(ODIR)/*.o *~ core $(INCDIR)/*~ 

The error I generate says:

gcc -o hellomake  -I../include
gcc: fatal error: no input files
compilation terminated.
make: *** [hellomake] Error 4

What's happening?

like image 335
iBeyondPower Avatar asked Jun 01 '15 12:06

iBeyondPower


People also ask

Do you need header files in makefile?

A make file simply saves if a, b, c, if newer than x then build x. is essentially the structure. So remove the header file is ok as long as you do not modify the header file.

Should makefile be in src directory?

Since these files normally appear in the source directory, they should always appear in the source directory, not in the build directory. So Makefile rules to update them should put the updated files in the source directory.

How do I run a makefile in another folder?

Use cd ./dir && make && pwd inside Makefile . The && was exactly what I needed to change a directory and execute a command there, then drop back to the main folder to finish the build.

How do I add a header in makefile?

The only way to include the header file is to treat the filename in the same way you treat a string. Makefiles are a UNIX thing, not a programming language thing Makefiles contain UNIX commands and will run them in a specified sequence.


1 Answers

Your tutorial promotes old and bad practices, you should avoid it IMHO.

In your rule here:

$(ODIR)/%.o: %.c $(DEPS)

You're telling make to look for sources in the current directory while they actually reside in the src directory, thus this pattern is never used and you have no suitable one.


Make sure you organize your project directory like this :

root
├── include/
│   └── all .h files here
├── lib/
│   └── all third-party library files (.a/.so files) here
├── src/
│   └── all .c files here
└── Makefile

Then let's take the process step by step, using good practices.

Firstly, don't define anything if you don't need to. Make has a lot of predefined variables and functions that you should use before trying to do it manually. In fact, he has so many that you can compile a simple file without even having a Makefile in the directory at all!

  1. List your source and build output directories:

     SRC_DIR := src
     OBJ_DIR := obj
     BIN_DIR := bin # or . if you want it in the current directory
    
  2. Name your final target, that is, your executable:

     EXE := $(BIN_DIR)/hellomake
    
  3. List your source files:

     SRC := $(wildcard $(SRC_DIR)/*.c)
    
  4. From the source files, list the object files:

     OBJ := $(SRC:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)
     # You can also do it like that
     OBJ := $(patsubst $(SRC_DIR)/%.c, $(OBJ_DIR)/%.o, $(SRC))
    
  5. Now let's handle the flags

     CPPFLAGS := -Iinclude -MMD -MP # -I is a preprocessor flag, not a compiler flag
     CFLAGS   := -Wall              # some warnings about bad code
     LDFLAGS  := -Llib              # -L is a linker flag
     LDLIBS   := -lm                # Left empty if no libs are needed
    

(CPP stands for C PreProcessor here, not CPlusPlus! Use CXXFLAGS for C++ flags and CXX for C++ compiler.)

The -MMD -MP flags are used to generate the header dependencies automatically. We will use this later on to trigger a compilation when only a header changes.

Ok, time to roll some recipes now that our variables are correctly filled.

It is widely spread that the default target should be called all and that it should be the first target in your Makefile. Its prerequisites shall be the target you want to build when writing only make on the command line:

all: $(EXE)

One problem though is Make will think we want to actually create a file or folder named all, so let's tell him this is not a real target:

.PHONY: all

Now list the prerequisites for building your executable, and fill its recipe to tell make what to do with these:

$(EXE): $(OBJ)
    $(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@

(CC stands for C Compiler.)

Note that your $(BIN_DIR) might not exist yet so the call to the compiler might fail. Let's tell make that you want it to check for that first:

$(EXE): $(OBJ) | $(BIN_DIR)
    $(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@

$(BIN_DIR):
    mkdir -p $@

Some quick additional notes:

  • $(CC) is a built-in variable already containing what you need when compiling and linking in C
  • To avoid linker errors, it is strongly recommended to put $(LDFLAGS) before your object files and $(LDLIBS) after
  • $(CPPFLAGS) and $(CFLAGS) are useless here, the compilation phase is already over, it is the linking phase here

Next step, since your source and object files don't share the same prefix, you need to tell make exactly what to do since its built-in rules don't cover your specific case:

$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
    $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@

Same problem as before, your $(OBJ_DIR) might not exist yet so the call to the compiler might fail. Let's update the rules:

$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR)
    $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@

$(BIN_DIR) $(OBJ_DIR):
    mkdir -p $@

Ok, now the executable should build nicely. We want a simple rule to clean the build artifacts though:

clean:
    @$(RM) -rv $(BIN_DIR) $(OBJ_DIR) # The @ disables the echoing of the command

(Again, clean is not a target that needs to be created, so add it to the .PHONY special target!)

Last thing. Remember about the automatic dependency generation? GCC and Clang will create .d files corresponding to your .o files, which contains Makefile rules for us to use, so let's include that in here:

-include $(OBJ:.o=.d) # The dash is used to silence errors if the files don't exist yet

Final result:

SRC_DIR := src
OBJ_DIR := obj
BIN_DIR := bin

EXE := $(BIN_DIR)/hellomake
SRC := $(wildcard $(SRC_DIR)/*.c)
OBJ := $(SRC:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)

CPPFLAGS := -Iinclude -MMD -MP
CFLAGS   := -Wall
LDFLAGS  := -Llib
LDLIBS   := -lm

.PHONY: all clean

all: $(EXE)

$(EXE): $(OBJ) | $(BIN_DIR)
    $(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@

$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR)
    $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@

$(BIN_DIR) $(OBJ_DIR):
    mkdir -p $@

clean:
    @$(RM) -rv $(BIN_DIR) $(OBJ_DIR)

-include $(OBJ:.o=.d)
like image 110
Chnossos Avatar answered Oct 07 '22 21:10

Chnossos