0
votes

I am trying to create a Makefile to compile my C program. I am trying to generate a Debug and Release build with the following directory structure:

helloworld/
├── Debug/
│   ├── bin/
│   ├── depends/
│   └── obj/
├── Release/
│   ├── bin/
│   ├── depends/
│   └── obj/
├── mylib/
│   ├── bar.c
│   ├── bar.h
│   ├── foo.c
│   └── foo.h
└── main.c

What I want to do is have the Makefile include different dependency files based on the target (Debug vs Release). The makefile I have right now is this:

CC = clang

CFLAGS = -c -g -Wall -Wextra -pedantic

SOURCES = main.c foo.c bar.c

OBJECTS = $(SOURCES:%.c=Debug/obj/%.o)
INCLUDE = -Imylib/
VPATH   =   mylib

.PHONY: debug

debug: Debug/bin/main

Debug/bin/main: $(OBJECTS)
    $(CC) $^ -o -g $@

Debug/obj/%.o: %.c | Debug
    $(CC) $(CFLAGS) $< -o $@ -MMD -MP -MF Debug/depends/$(@F:.o=.d) $(INCLUDE)

-include $(SOURCES:%.c=Debug/depends/%.d)

Debug:
    -mkdir Debug
    -mkdir Debug/bin
    -mkdir Debug/depends
    -mkdir Debug/obj

clean:
    rm -rf Debug
    rm -rf Release

Which works great for just debug. But when I try to create separate targets for Debug and Release I'm stumped with how to modify -include $(SOURCES:%.c=Debug/depends/%.d)

CC = clang

CFLAGS = -c -g -Wall -Wextra -pedantic

SOURCES = main.c foo.c bar.c
DEBUG_OBJECTS = $(SOURCES:%.c=Release/obj/%.o)
RELEASE_OBJECTS = $(SOURCES:%.c=Debug/obj/%.o)
INCLUDE = -Imylib/
VPATH   =   mylib

.PHONY: all debug release

all: release debug
release: Release/bin/main
debug: Debug/bin/main

Release/bin/main: $(RELEASE_OBJECTS)
    $(CC) $^ -o $@

Release/obj/%.o: %.c | Release
    $(CC) $(CFLAGS) $< -o $@ -MMD -MP -MF Release/depends/$(@F:.o=.d) $(INCLUDE)

Debug/bin/main: $(DEBUG_OBJECTS)
    $(CC) $^ -o -g $@

Debug/obj/%.o: %.c | Debug
    $(CC) $(CFLAGS) $< -o $@ -MMD -MP -MF Debug/depends/$(@F:.o=.d) $(INCLUDE)

# -------------------------------------------------------
# -include $(SOURCES:%.c=Debug/depends/%.d) What do I do?
# -------------------------------------------------------

Release:
    -mkdir Release
    -mkdir Release/bin
    -mkdir Release/depends
    -mkdir Release/obj

Debug:
    -mkdir Debug
    -mkdir Debug/bin
    -mkdir Debug/depends
    -mkdir Debug/obj

clean:
    rm -rf Debug
    rm -rf Release

Questions

  1. How can I conditionally include dependency files based on the target?

  2. Is there a better project organization structure?

  3. I'm extremely new to makefiles. Previously I've just relied on my IDEs to manage building and configurations. Are there any other tips to help improve this makefile?

1
And do you already know what you want make to do when you invoke it with make debug release (or make all)? Which dependency files shall be included?Renaud Pacalet
My intent was that Debug/depends would be included whenever the debug executable is built while Release/depends would be included whenever the release executable is built. Is it even possible to switch includes partway through creating a recipe? If not, I'll be happy with dropping make all and using just make debug and make release.thndrwrks
A file is included or not, this cannot depend on the currently built target. Else, you could build only one target per make invocation, and it would really be a pity. Would you accept a solution based on recursive make (that is, make invoking make)? There is a debate on whether this is good or bad (as usual, it is good when it helps and bad when it makes things worse).Renaud Pacalet
I hadn't considered recursive make but in this case it looks like it would solve the problem.thndrwrks

1 Answers

0
votes

Ok I think I've figured out one possible solution: Use two makefiles. The first makefile creates the object files and creates the final executable. The second makefile calls the first makefile with different variables.

makefile

export CC := gcc
export SOURCES  := main.c bar.c foo.c
export VPATH    := mylib/
export INCLUDES := $(VPATH:%=-I%)
export DBGFLAGS := -g -c -Wall -Wextra -pedantic
export RELFLAGS :=    -c -Wall -Wextra -pedantic -Os

.PHONY: all debug release

all: debug release

release:
    make -f target.mk TARGET=Release CFLAGS='$(RELFLAGS)'

debug:
    make -f target.mk TARGET=Debug CFLAGS='$(DBGFLAGS)'

clean:
    make -f target.mk clean TARGET=Debug
    make -f target.mk clean TARGET=Release

target.mk

$(TARGET)/bin/main.out: $(SOURCES:%.c=$(TARGET)/obj/%.o)
    $(CC) $^ -o $@

$(TARGET)/obj/%.o: %.c | $(TARGET)
    $(CC) $(CFLAGS) $< -o $@ -MMD -MP -MF $(TARGET)/depends/$(@F:.o=.d) $(INCLUDES)

$(TARGET):
    -mkdir $(TARGET)
    -mkdir $(TARGET)/bin
    -mkdir $(TARGET)/depends
    -mkdir $(TARGET)/obj

clean:
    rm -rf $(TARGET)

-include $(SOURCES:%.c=$(TARGET)/depends/%.d)