103

A few months ago, I came up with the following generic Makefile for school assignments:

# ------------------------------------------------
# Generic Makefile
#
# Author: yanick.rochon@gmail.com
# Date  : 2010-11-05
#
# Changelog :
#   0.01 - first version
# ------------------------------------------------

# project name (generate executable with this name)
TARGET   = projectname

CC       = gcc -std=c99 -c
# compiling flags here
CFLAGS   = -Wall -I.

LINKER   = gcc -o
# linking flags here
LFLAGS   = -Wall

SOURCES  := $(wildcard *.c)
INCLUDES := $(wildcard *.h)
OBJECTS  := $(SOURCES:.c=*.o)
rm       = rm -f

$(TARGET): obj
    @$(LINKER) $(TARGET) $(LFLAGS) $(OBJECTS)
    @echo "Linking complete!"

obj: $(SOURCES) $(INCLUDES)
    @$(CC) $(CFLAGS) $(SOURCES)
    @echo "Compilation complete!"

clean:
    @$(rm) $(TARGET) $(OBJECTS)
    @echo "Cleanup complete!"

This will basically compile every .c and .h file to generate .o files and the executable projectname all in the same folder.

Now, I'd like to push this a little. How can I write a Makefile to compile a C project with the following directory structure?

 ./
 ./Makefile
 ./src/*.c;*.h
 ./obj/*.o
 ./bin/<executable>

In other words, I'd like to have a Makefile that compiles C sources from ./src/ into ./obj/ and then link everything to create the executable in ./bin/.

I've tried to read different Makefiles, but I simply can't make them work for the project structure above; instead, the project fails to compile with all sorts of errors. Sure, I could use full blown IDE (Monodevelop, Anjuta, etc.), but I honestly prefer to stick with gEdit and the good ol' terminal.

Is there a guru who can give me a working solution, or clear information about how this can be done? Thank you!

** UPDATE (v4) **

The final solution :

# ------------------------------------------------
# Generic Makefile
#
# Author: yanick.rochon@gmail.com
# Date  : 2011-08-10
#
# Changelog :
#   2010-11-05 - first version
#   2011-08-10 - added structure : sources, objects, binaries
#                thanks to http://stackoverflow.com/users/128940/beta
#   2017-04-24 - changed order of linker params
# ------------------------------------------------

# project name (generate executable with this name)
TARGET   = projectname

CC       = gcc
# compiling flags here
CFLAGS   = -std=c99 -Wall -I.

LINKER   = gcc
# linking flags here
LFLAGS   = -Wall -I. -lm

# change these to proper directories where each file should be
SRCDIR   = src
OBJDIR   = obj
BINDIR   = bin

SOURCES  := $(wildcard $(SRCDIR)/*.c)
INCLUDES := $(wildcard $(SRCDIR)/*.h)
OBJECTS  := $(SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o)
rm       = rm -f


$(BINDIR)/$(TARGET): $(OBJECTS)
    @$(LINKER) $(OBJECTS) $(LFLAGS) -o $@
    @echo "Linking complete!"

$(OBJECTS): $(OBJDIR)/%.o : $(SRCDIR)/%.c
    @$(CC) $(CFLAGS) -c $< -o $@
    @echo "Compiled "$<" successfully!"

.PHONY: clean
clean:
    @$(rm) $(OBJECTS)
    @echo "Cleanup complete!"

.PHONY: remove
remove: clean
    @$(rm) $(BINDIR)/$(TARGET)
    @echo "Executable removed!"
Yanick Rochon
  • 51,409
  • 25
  • 133
  • 214

3 Answers3

37

First, your $(OBJECTS) rule is problematic, because:

  1. it's kind of indiscriminate, making all sources prerequisites of every object,
  2. it often uses the wrong source (as you discovered with file1.o and file2.o)
  3. it tries to build executables instead of stopping at objects, and
  4. the name of the target (foo.o) is not what the rule will actually produce (obj/foo.o).

I suggest the following:

OBJECTS  := $(SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o)

$(OBJECTS): $(OBJDIR)/%.o : $(SRCDIR)/%.c
    $(CC) $(CFLAGS) -c $< -o $@
    @echo "Compiled "$<" successfully!"

The $(TARGET) rule has the same problem that the target name does not actually describe what the rule builds. For that reason, if you type make several times, Make will rebuild the target each time, even though there is no reason to. A small change fixes that:

$(BINDIR)/$(TARGET): $(OBJECTS)
    $(LINKER) $@ $(LFLAGS) $(OBJECTS)
    @echo "Linking complete!"

Once that's all in order, you might consider more sophisticated dependency handling; if you modify one of the header files, this makefile will not know which objects/executables must be rebuilt. But that can wait for another day.

EDIT:
Sorry, I omitted part of the $(OBJECTS) rule above; I've corrected it. (I wish I could use "strike" inside a code sample.)

zero298
  • 25,467
  • 10
  • 75
  • 100
Beta
  • 96,650
  • 16
  • 149
  • 150
  • with your suggested changes, I get : `obj/file1.o: In function 'main': \n main.c:(.text+0x0): multiple definition of 'main' \n obj/main.o:main.c:(.text+0x0): first defined here` – Yanick Rochon Aug 10 '11 at 05:18
  • @Yanick Rochon: Do you have multiple `main` functions? Maybe one in `file1.c` and one in `main.c`? If so then you will not be able to link these objects; there can be only one `main` in an executable. – Beta Aug 10 '11 at 05:49
  • No, I do not. Everything works fine with the last version I posted in the question. When I change my Makefile to what you suggest (and I do understand the benefits of what you're saying) that's what I get. I just pasted `file1.c` but it gives the same message to every single files of the project. And `main.c` is the **only** one with a main function... and `main.c` imports `file1.h` and `file2.h` (there's no relation between `file1.c` and `file2.c`), but I doubt the problem comes from there. – Yanick Rochon Aug 10 '11 at 06:25
  • @Yanick Rochon: I made a mistake pasting the first line of my `$(OBJECTS)` rule; I've edited it. With the bad line I got an error, but not the one you got... – Beta Aug 10 '11 at 12:48
7

You can add the -I flag to the compiler flags (CFLAGS) to indicate where the compiler should look for source files , and the -o flag to indicate where the binary should be left:

CFLAGS   = -Wall -I./src
TARGETPATH = ./bin

$(TARGET): obj
    @$(LINKER) $(TARGETPATH)/$(TARGET) $(LFLAGS) $(OBJECTS)
    @echo "Linking complete!"

In order to drop the object files into the obj directory, use the -o option when compiling. Also, look at the $@ and $< automatic variables.

For example, consider this simple Makefile

CFLAGS= -g -Wall -O3                                                            
OBJDIR= ./obj

SRCS=$(wildcard *.c)
OBJS=$(SRCS:.c=.o )
all:$(OBJS)

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

Update>

By looking at your makefile, I realize you are using the -o flag. Good. Continue using it, but add a target directory variable to indicate where the output file should be written.

Tom
  • 43,810
  • 29
  • 138
  • 169
  • could you be more specific? Do you mean adding `-l ...` to the `CFLAGS` and ... there's already the `-o` argument to the linker (`LINKER`) – Yanick Rochon Aug 10 '11 at 01:03
  • Yes, the CFLAGS, and yes, keep using -o, just add the TARGETPATH variable. – Tom Aug 10 '11 at 01:07
  • Thank you, I have made the modifications, but it seems I'm still missing somethings (see the update on the question) – Yanick Rochon Aug 10 '11 at 01:16
  • just `make`, from where the Makefile sits – Yanick Rochon Aug 10 '11 at 01:25
  • Can't you read the command being executed? for example gcc -c yadayada. Pretty sure there's a variable that doesn't contain what you expect – Tom Aug 10 '11 at 01:41
  • @YanickRochon let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/2302/discussion-between-tom-and-yanick-rochon) – Tom Aug 10 '11 at 01:41
  • Sorry Tom, I was somewhere else for a while, after yet another attempt. I went into the chat and saw your "No one there :(". Sorry – Yanick Rochon Aug 10 '11 at 01:44
  • `%.o: %.c` rule is wrong. It does not produce the target it promises to produce. Should be `${OBJDIR}/%.o: %.c` and `-o $@`. – Maxim Egorushkin Aug 10 '11 at 07:57
-3

I have stopped writing makefiles these days, if your intention is to learn go ahead, else you have good makefile generator that comes with eclipse CDT. If you want some maintainability / multiple project support with in your build tree, have a look at the following -

https://github.com/dmoulding/boilermake I found this pretty good..!

Kamath
  • 4,461
  • 5
  • 33
  • 60