1
votes

I want to create a make file that takes all files in several src subdirectories and compiles them each directly into one single build directory.

I.e. i have e.g.

  • src/main.c
  • src/i2c/i2c.c
  • src/i2c/i2c.h

and as output i want the object files as well as the final binary - build/main.o - build/i2c.o - build/release.elf

I manage to get all source files as a list with their respective subdirectory paths into a variable and I also manage to get a list of all output files but when i try to create a target to build all .o files in that build directory it does not match the corresponding .c files with the .o files. Here i am just not sure how to link these two.

It fails while trying to match main.o with i2c.c.

Here is "relevant" part of the Makefile:

TARGET = $(lastword $(subst /, ,$(CURDIR)))

BUILD_DIR  := buildDir

SOURCES = $(wildcard src/*.c src/*/*.c)
BROKENOBJECTS = $(SOURCES:.c=.o)
LESSBROKEN = $(notdir $(BROKENOBJECTS))
OBJECT_FILES = $(addprefix $(BUILD_DIR)/, $(LESSBROKEN))

$(BUILD_DIR)/%.o: $(SOURCES) $(BUILD_DIR)
    $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c -o $@ $<

$(BUILD_DIR)/$(TARGET).elf: $(OBJECT_FILES)
    $(CC) $(LDFLAGS) $(TARGET_ARCH) $^ $(LDLIBS) -o $@

$(BUILD_DIR) :
    mkdir -p $@

compile : $(BUILD_DIR)/$(TARGET).elf

How would I go about this, running the recipe for each .c file from $(SOURCES) and just create the corresponding .o file in buildDir/ ?

3
Do all of your *.c files have unique names? i.e. if there is a file i2c.c in the directory src/i2c then no other source directory will have a file named i2c.c. - G.M.
For now it won't. I might have a case in the distant future where i want to link a different implementation to that, but in that case i think i would "just" include a different source folder with that specific implementation into it. Or add that source file specifically. For now everything existing should be included. - Saphieron

3 Answers

1
votes

Assuming you use GNU make and your C source files are all *.c that can be found in the current directory and all its subdirectories (up to any depth), this should be close to what you want:

BUILDDIR := build
SRC      := $(shell find . -type f -name '*.c')

# Convert C source file name(s) to object file name(s)
# $(1): C source file name(s)
define c2o
$(patsubst %.c,$(BUILDDIR)/%.o,$(notdir $(1)))
endef

OBJ := $(call c2o,$(SRC))

.PHONY: all

all: $(OBJ)

# Compilation rule for a C source file (use echo for testing)
# $(1): C source file name
define MY_rule
$$(call c2o,$(1)): $(1)
    @echo $$(CC) $$(CFLAGS) $$(CPPFLAGS) $$(TARGET_ARCH) -c -o $$@ $$<
endef

# Instantiate compilation rules for all C source files
$(foreach s,$(SRC),$(eval $(call MY_rule,$(s))))

Demo:

host> tree .
.
├── Makefile
├── a.c
├── b
│   └── b.c
└── c
    └── c
        └── c.c

host> make
cc -c -o build/a.o a.c
cc -c -o build/c.o c/c/c.c
cc -c -o build/b.o b/b.c

Note the use of $$ in the definition of MY_rule. It is needed because it gets expanded twice: one time when expanding the parameters of the eval function and a second time when make parses the result as regular make syntax.

As explained in other comments and answers this works only if you don't have several C source files with the same base name. There is a way to detect this situation and issue an error if it is encountered. The make sort function sorts its word list parameter but it also removes duplicates. So, if the word count before and after sorting differ, you have duplicates. Add the following just after the definition of OBJ:

SOBJ     := $(sort $(OBJ))

ifneq ($(words $(OBJ)),$(words $(SOBJ)))
$(error Found multiple C source files with same base name)
endif

Demo:

host> touch c/c/a.c
host> make
Makefile:13: *** Found multiple C source files with same base name.  Stop.
0
votes

Here is a modified snippet that should do what you want, though I didn't find a solution without specifying each subdirectory in src/ manually.

SOURCES = $(wildcard src/*.c)
SUBSOURCES = $(wildcard src/*/*.c)

OBJECTS = $(addprefix $(BUILD_DIR)/, $(notdir $(SOURCES:.c=.o)))
SUBOBJECTS = $(addprefix $(BUILD_DIR)/, $(notdir $(SUBSOURCES:.c=.o)))

compile : $(BUILD_DIR)/$(TARGET).elf

$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) $(SUBOBJECTS)
    $(CC) $(LDFLAGS) $(TARGET_ARCH) $^ $(LDLIBS) -o $@

# save some typing for the rules below
COMPILE = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c -o $@ $<

$(OBJECTS): $(BUILD_DIR)/%.o: src/%.c | $(BUILD_DIR)
    $(COMPILE)

$(BUILD_DIR)/%.o: src/i2c/%.c | $(BUILD_DIR)
    $(COMPILE)

$(BUILD_DIR)/%.o: src/someOtherSubdir/%.c | $(BUILD_DIR)
    $(COMPILE)

As @G.M. suggested in the comments, you must make sure that source file names are unique across subdirectories. Note also that I turned $(BUILD_DIR) into an order only prerequisite, which should reflect your intention more precisely.

0
votes

You could make use of make's vpath mechanism. So, rather than specifying possible source paths using...

SOURCES = $(wildcard src/*.c src/*/*.c)

you would have...

# Build a list of directories under src
#
SOURCE_DIRS := $(shell find src -type d)

# Use the list in $(SOURCE_DIRS) as a search path for .c files.
#
vpath %.c $(SOURCE_DIRS)

Now, when attempting to update i2c.o (for example), the rule...

$(BUILD_DIR)/%.o: %.c $(BUILD_DIR)
    $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c -o $@ $<

will cause make to automatically search through the list of source directories for the dependency i2c.c.


Note: For obvious reasons multiple files with the same name under different source directories will cause problems here. Hence my original question (in the comments) regarding the uniqueness of source file names under different directories.