Makefile to compile lists of source files - makefile

I have lists of files that I want my Makefile to compile, one list for each source language:
CFILES= Src/Application/main.c Src/Core/data.c Lib/routines.c
ASFILES= Src/Application/startup.s Lib/sqrt.s
I want all the output in one directory:
OBJDIR= output
How do I do the equivalent of:
output/main.o : Src/Application/main.c
cc -c -o output/main.o Src/Application/main.c
output/data.o : Src/Core/data.c
cc -c -o output/data.o Src/Core/data.c
output/routines.o : Lib/routines.c
cc -c -o output/routines.o Lib/routines.c
output/startup.o : Src/Application/startup.s
as -o output/startup.o Src/Application/startup.s
output/sqrt.o : Lib/sqrt.s
as -o output/sqrt.o Lib/sqrt.s
The recipes are the same for every file in its list.
The input files are in all sorts of different directories and it is not acceptable to just list their filenames and use a search path to find them, their explicit paths must be used.
The output filename is the basename of the source file name with the extension changed to o. There are no duplicated basenames between the lists for the different source languages.
I do not want to have to list the object files, this should be derived from the source lists.
I am using gnu make, but bonus points for a portable solution.

Something like the following could do:
all :
OBJDIR := output
CFILES := Src/Application/main.c Src/Core/data.c Lib/routines.c
ASFILES := Src/Application/startup.s Lib/sqrt.s
target = ${OBJDIR}/$(patsubst %.s,%.o,$(notdir ${1}))
obj.c :=
obj.s :=
define obj
$(call target,${1}) : ${1} | ${OBJDIR}
obj$(suffix ${1}) += $(call target,${1})
${1} : ; mkdir -p `dirname $$#` && touch $$# # Create the source for testing. Remove this.
endef
define SOURCES
$(foreach src,${1},$(eval $(call obj,${src})))
endef
$(eval $(call SOURCES,${CFILES}))
$(eval $(call SOURCES,${ASFILES}))
all : ${obj.c} ${obj.s}
${obj.c} : % :
#echo cc -c -o $# $^; touch $# # echo and touch are for testing. Remove these.
${obj.s} : % :
#echo as -o $# $^; touch $# # echo and touch are for testing. Remove these.
${OBJDIR} :
mkdir $#
.PHONY: all
Output:
$ make
make: Entering directory '/home/max/tmp'
mkdir -p `dirname Src/Application/main.c` && touch Src/Application/main.c # Create the source for testing. Remove this.
mkdir output
cc -c -o output/main.c Src/Application/main.c
mkdir -p `dirname Src/Core/data.c` && touch Src/Core/data.c # Create the source for testing. Remove this.
cc -c -o output/data.c Src/Core/data.c
mkdir -p `dirname Lib/routines.c` && touch Lib/routines.c # Create the source for testing. Remove this.
cc -c -o output/routines.c Lib/routines.c
mkdir -p `dirname Src/Application/startup.s` && touch Src/Application/startup.s # Create the source for testing. Remove this.
as -o output/startup.o Src/Application/startup.s
mkdir -p `dirname Lib/sqrt.s` && touch Lib/sqrt.s # Create the source for testing. Remove this.
as -o output/sqrt.o Lib/sqrt.s
make: Leaving directory '/home/max/tmp'

I have little experience in writing makefiles, so this is just an attempt. In my example I have C and C++ files in a few directories and build a program made of these files.
$ cat Makefile
.PHONY : clean all
CC=gcc
CXX=g++
CFILES = c/f.c c/g.c
CPPFILES = cpp/main.cpp
OUTPUT = ./output
SOURCE_DIRS := $(dir $(CFILES))
SOURCE_DIRS += $(dir $(CPPFILES))
VPATH = $(sort $(SOURCE_DIRS))
C_FILENAMES := $(notdir $(CFILES))
CPP_FILENAMES += $(notdir $(CPPFILES))
OBJ_FILES := $(patsubst %.c, $(OUTPUT)/%.o, $(C_FILENAMES) )
OBJ_FILES += $(patsubst %.cpp, $(OUTPUT)/%.o, $(CPP_FILENAMES) )
all : $(OUTPUT)/program
$(OUTPUT)/program : $(OBJ_FILES)
g++ -o $# $^
$(OUTPUT)/%.o : %.cpp
$(shell mkdir -p $(OUTPUT) )
$(CXX) $(CXXFLAGS) -c $< -o $#
$(OUTPUT)/%.o : %.c
$(shell mkdir -p $(OUTPUT) )
$(CC) $(CFLAGS) -c $< -o $#
clean:
rm -fr $(OUTPUT)
And this is an example of using my makefile:
$ make
gcc -c c/f.c -o output/f.o
gcc -c c/g.c -o output/g.o
g++ -c cpp/main.cpp -o output/main.o
g++ -o output/program output/f.o output/g.o output/main.o

The following method should do what you want: compile all of your specified source files and put the object files in the directory ./output automatically. Of course, you need to provide compiler options, proper libraries necessary for linking, and so on.
OBJDIR =./output
SRCDIR1 =./Src/Application
SRCDIR2 =./Src/Core
SRCDIR3 =./Lib
SRC1 =$(SRCDIR1)/main.c
SRC2 =$(SRCDIR2)/data.c
SRC3 =$(SRCDIR3)/routines.c
SRC4 =$(SRCDIR1)/startup.s
SRC5 =$(SRCDIR3)/sqrt.s
OBJ1 =$(patsubst $(SRCDIR1)/%.c,$(OBJDIR)/%.o,$(SRC1))
OBJ2 =$(patsubst $(SRCDIR2)/%.c,$(OBJDIR)/%.o,$(SRC2))
OBJ3 =$(patsubst $(SRCDIR3)/%.c,$(OBJDIR)/%.o,$(SRC3))
OBJ4 =$(patsubst $(SRCDIR1)/%.s,$(OBJDIR)/%.o,$(SRC4))
OBJ5 =$(patsubst $(SRCDIR3)/%.s,$(OBJDIR)/%.o,$(SRC5))
vpath %.c $(SRCDIR1): $(SRCDIR2): $(SRCDIR3)
vpath %.s $(SRCDIR1): $(SRCDIR3)
all: $(OBJ1) $(OBJ2) $(OBJ3) $(OBJ4) $(OBJ5)
cc $^ -o executable
$(OBJDIR)/%.o: %.c | $(OBJDIR)
cc -c $< -o $#
$(OBJDIR)/%.o: %.s | $(OBJDIR)
cc -c $< -o $#
$(OBJDIR):
mkdir -p $(OBJDIR)

Related

how to factorise makefile targets?

I have the following 'working' snippet:
c_file_path := toto.c
asm_file_path := toto.S
c_files = $(notdir $(c_file_path))
asm_files = $(notdir $(asm_file_path))
vpath %.c $(dir $(c_file_path))
vpath %.S $(dir $(asm_file_path))
OBJDIR=objs/
c_objs:=$(addprefix $(OBJDIR),$(patsubst %.c,%.o,$(c_files)))
asm_objs:=$(addprefix $(OBJDIR),$(patsubst %.S,%.S.o,$(asm_files)))
$(info c_files $(c_files))
$(info s_files $(asm_files))
$(info c_objs $(c_objs))
$(info asm_objs $(asm_objs))
%.c:
#touch $#
%.S:
#touch $#
objs/%.o:%.c
#mkdir -p $(OBJDIR)
#gcc -c $^ -o $#
objs/%.S.o:%.S
#mkdir -p $(OBJDIR)
#gcc -c $^ -o $#
clean:
#rm -rf objs/*
all: $(c_objs) $(asm_objs)
#ls $(c_objs) $(asm_objs)
#echo "hello"
I tried to merge targets:
c_file_path := toto.c
asm_file_path := toto.S
c_files = $(notdir $(c_file_path))
asm_files = $(notdir $(asm_file_path))
vpath %.c $(dir $(c_file_path))
vpath %.S $(dir $(asm_file_path))
OBJDIR=objs/
c_objs:=$(addprefix $(OBJDIR),$(patsubst %.c,%.o,$(c_files)))
asm_objs:=$(addprefix $(OBJDIR),$(patsubst %.S,%.S.o,$(asm_files)))
$(info c_files $(c_files))
$(info s_files $(asm_files))
$(info c_objs $(c_objs))
$(info asm_objs $(asm_objs))
%.c %.S:
#touch $#
objs/%.o objs/%.S.o:%.c %.S
#mkdir -p $(OBJDIR)
#gcc -c $^ -o $#
clean:
#rm -rf objs/*
all: $(c_objs) $(asm_objs)
#ls $(c_objs) $(asm_objs)
#echo "hello"
which gives:
gcc: error: toto.c no such file or directory
why weren't target merged?
how to merge targets?
I'm not sure what you're trying to do but this rule:
objs/%.o objs/%.S.o:%.c %.S
tells make that for two files foo.c and foo.S (for any string foo) they can be used to create the targets objs/foo.o and objs/foo.S.o by invoking the recipe (gcc) one time.
I'm pretty sure that that's not what you want to say because it means you can't use this pattern unless both files foo.c and foo.S exist for a given string foo.
If what you're asking is how to reduce these two pattern rules:
objs/%.o:%.c
#mkdir -p $(OBJDIR)
#gcc -c $^ -o $#
objs/%.S.o:%.S
#mkdir -p $(OBJDIR)
#gcc -c $^ -o $#
into a single pattern rule so you don't have to write it twice the answer is very simple:
You can't.
You could put the recipe into a variable, like this:
build = #mkdir -p $(OBJDIR) && gcc -c $^ -o $#
then you can simplify the pattern rules somewhat like this:
objs/%.o : %.c ; $(build)
objs/%.S.o : %.S ; $(build)
but that's the best you can do, without resorting to overly-complicated things like foreach/eval/call loops etc.

ERROR: rm: cannot remove 'kernel.img': No such file or directory

I've been trying to get my Raspberry Pi 4 OS (not Linux or anything, I'm making an OS from scratch) to work.
The Makefile has this error when I use the command "make":
rm -rf objects
rm -rf SuperPiOS.elf
rm SuperPiOS.img
rm: cannot remove 'SuperPiOS.img': No such file or directory
make: *** [Makefile:110: clean] Error 1
I can't figure out why it wouldn't work though.
Here's the Makefile:
CFLAGS= -Wall -O2 -ffreestanding -nostdinc -nostdlib -mcpu=cortex-a72+nosimd
CXXFLAGS= -ggdb3 -O0 -Wall -O2 -ffreestanding -nostdinc -nostdlib -mcpu=cortex-a72+nosimd
CSRCFLAGS= -O2 -Wall -Wextra
LFLAGS= -ffreestanding -O2 -nostdlib
IMG_PATH= ../
CFILES= $(wildcard *.c)
OFILES= $(CFILES:.c=.o)
GCCFLAGS= -Wall -O2 -ffreestanding -nostdinc -nostdlib -nostartfiles
GCCPATH= gcc-arm-10.2-2020.11-x86_64-aarch64-none-elf
GCCPATHAARCH= $(GCCPATH)/aarch64-none-elf/bin
GCCPATHBIN= $(GCCPATH)/bin
ASMCFLAGS= -f elf32 -F dwarf -g -w+all
ASM= -s
# Location of the files
KER_SRC = ../src/kernel
KER_MENU_SRC = ../src/kernel/menu
KER_HEAD = ../include
COMMON_SRC = ../src/common
UI_IMAGES = ../images/ui
SPE_GAMES = ../spe_games
DINOBYTE = $(SPE_GAMES)/dinobyte
OBJ_DIR = objects
ASMSOURCES = $(wildcard $(KER_SRC)/*.s)
KERSOURCES = $(wildcard $(KER_SRC)/*.c)
#KERSOURCES = $(wildcard $(KER_SRC)/$(ARCHDIR)/*.c)
COMMONSOURCES = $(wildcard $(COMMON_SRC)/*.c)
KERSOURCESCPP = $(wildcard $(KER_SRC)/*.cpp)
DINOBYTESOURCES = $(wildcard $(DINOBYTE)/src/*.cpp)
#KERSOURCESCPP = $(wildcard $(KER_SRC)/$(ARCHDIR)/*.cpp)
#KERMENUSOURCESC = $(wildcard $(KER_MENU_SRC)/*.c)
#KERMENUSOURCESCPP = $(wildcard $(KER_MENU_SRC)/*.cpp)
UISOURCES = $(wildcard $(UI_IMAGES)/*.png)
OBJECTS = $(patsubst $(KER_SRC)/%.s, $(OBJ_DIR)/%.o, $(ASMSOURCES))
#OBJECTS += $(patsubst $(KER_SRC)/%.s, $(OBJ_DIR)/%.o, $(ASMSOURCES))
OBJECTS += $(patsubst $(KER_SRC)/%.c, $(OBJ_DIR)/%.o, $(KERSOURCES))
OBJECTS += $(patsubst $(KER_SRC)/%.cpp, $(OBJ_DIR)/%.o, $(KERSOURCESCPP))
OBJECTS += $(patsubst $(COMMON_SRC)/%.c, $(OBJ_DIR)/%.o, $(COMMONSOURCES))
#OBJECTS += $(patsubst $(KER_MENU_SRC)/%.c, $(OBJ_DIR)/%.o, $(KERMENUSOURCESC))
#OBJECTS += $(patsubst $(KER_MENU_SRC)/%.cpp, $(OBJ_DIR)/%.o, $(KERMENUSOURCESCPP))
#OBJECTS += $(patsubst $(UI_IMAGES)/%.png, $(OBJ_DIR)/%.o, $(UISOURCES))
#Dinobyte objects [include Dinobyte headers here] (do later)
#OBJECTS += $(patsubst $(DINOBYTESOURCES)/src/%.cpp, $(OBJ_DIR)/%.o, $(DINOBYTESOURCES))
#Headers
HEADERS = $(wildcard $(KER_HEAD)/*.h)
IMG_NAME=SuperPiOS
#build: $(OBJECTS) $(HEADERS)
#$(CC) -T linker.ld -o $(IMG_NAME).elf $(LFLAGS) $(OBJECTS) #needs indent
#$(OBJCOPY) $(IMG_NAME).elf -O binary $(IMG_NAME).img #needs indent
#$(OBJ_DIR)/%.o: $(KER_SRC)/%.s
#mkdir -p $(#D) #needs indent
#$(CC) $(CFLAGS) -I$(KER_SRC) -c $< -o $# #needs indent
$(OBJ_DIR)/%.o: $(KER_SRC)/%.s
$(GCCPATHBIN)/aarch64-none-elf-gcc $(GCCFLAGS) -c $(KER_SRC) -o $(OBJ_DIR)
$(OBJ_DIR)/%.o: $(KER_SRC)/%.c
$(GCCPATHBIN)/aarch64-none-elf-gcc $(GCCFLAGS) -c $< -o $#
$(OBJ_DIR)/%.o: $(KER_SRC)/$(ARCHDIR)/%.c
$(GCCPATHBIN)/aarch64-none-elf-gcc $(GCCFLAGS) -c $< -o $#
$(OBJ_DIR)/%.o: $(KER_SRC)/%.cpp
$(GCCPATHBIN) arm-none-eabi-cpp $(GCCFLAGS) -c $< -o $#
$(OBJ_DIR)/%.o: $(KER_SRC)/$(ARCHDIR)/%.cpp
$(GCCPATHBIN)/aarch64-none-elf-gcc $(GCCFLAGS) -c $< -o $#
$(OBJ_DIR)/%.o: $(COMMON_SRC)/%.c
$(GCCPATHBIN)/aarch64-none-elf-gcc $(GCCFLAGS) -c $< -o $#
#$(OBJ_DIR)/%.o: $(KER_MENU_SRC)/%.c
# mkdir -p $(#D)
# $(CC) $(CFLAGS) -I$(KER_SRC) -I$(KER_HEAD) -c $< -o $# $(CSRCFLAGS)
#$(OBJ_DIR)/%.o: $(KER_MENU_SRC)/%.cpp
# mkdir -p $(#D)
# $(CC) $(CXXFLAGS) -I$(KER_SRC) -I$(KER_HEAD) -c $< -o $# $(CSRCFLAGS)
$(IMG_NAME)%.img: $(OBJECTS) $(HEADERS)
$(GCCPATHBIN)/aarch64-none-elf-ld -nostdlib -nostartfiles $(OBJECTS) -T linker.ld -o $(IMG_NAME).elf
$(GCCPATHBIN)/aarch64-none-elf-objcopy -O binary $(IMG_NAME).elf $(IMG_NAME).img
clean:
rm -rf $(OBJ_DIR)
rm -rf $(IMG_NAME).elf
rm $(IMG_NAME).img
run: build
qemu-system-arm -m 128 -no-reboot -M raspi4 -serial stdio -kernel kernel.elf
dbg:
$(GDB) kernel.elf
dbgrun: build gdbinit
qemu-system-arm -m 128 -no-reboot -M raspi4 -serial stdio -kernel kernel.elf -S -s
.PHONY: gdbinit
gdbinit:
echo "target remote localhost:1234" > .gdbinit
echo "break kernel_main" >> .gdbinit
You have two problems. The most obvious one is that this command:
rm SuperPiOS.img
is failing because that's how the rm program is defined: if the file you ask it to delete doesn't exist then rm will fail. If you don't want that to happen, add the -f option:
rm -f SuperPiOS.img
Now if the file doesn't exist, rm will silently succeed. You can examine the man pages for the rm program.
However, the higher level question you are probably asking is, why when I run make is it running the clean rule?
That's because make, if you don't specify what to build on the command line, always builds the first explicit target. In this makefile the first explicit target you defined is clean, so that's what's built. This is almost certainly not what you want. You should figure out what target you want to build when you run make with no arguments and put the rule for that target as the first explicit target in your makefile.
Other than this rule it normally doesn't matter much what order rules come in (if you have multiple pattern rules that could all build the same target, for example multiple pattern rules to build %.o, then order can matter there).

Issue with Makefile recipe for inputting object files

I have a makefile, that I edited so that object files should go to a separate directory.
I edited the makefile and it is now doing what I wanted to, but during linker stage it is not working as expected because I am doing something wrong.
I have a sources file and makefile as shown below:
Sources File
TARGET = demo
SRC_DIR = modbus/ascii/ \
modbus/functions/ \
modbus/port/ \
modbus/rtu/ \
modbus/ \
./
INCLUDE_DIR = modbus/include/ \
modbus/port/ \
modbus/rtu \
modbus/ascii \
./
SOURCE = modbus/ascii/mbascii.c \
modbus/functions/mbfunccoils.c \
modbus/functions/mbfuncdiag.c \
modbus/functions/mbfuncdisc.c \
modbus/functions/mbfuncholding.c \
modbus/functions/mbfuncinput.c \
modbus/functions/mbfuncother.c \
modbus/functions/mbutils.c \
modbus/port/port.c \
modbus/port/portevent.c \
modbus/port/portserial.c \
modbus/port/porttimer.c \
modbus/rtu/mbcrc.c \
modbus/rtu/mbrtu.c \
modbus/mb.c \
demo.c \
startup_LPC17xx.c \
system_LPC17xx.c
Makefile
include ./sources
ARCH = arm-none-eabi
BUILD_DIR := build/objs
# Tool definitions
CC = $(ARCH)-gcc
LD = $(ARCH)-gcc
AR = $(ARCH)-ar
AS = $(ARCH)-as
CP = $(ARCH)-objcopy
OD = $(ARCH)-objdump
SIZE = $(ARCH)-size
RM = rm
Q = # #./quiet "$#"
# Flags
CFLAGS = -W -Wall -O0 --std=gnu99 -fgnu89-inline -mcpu=cortex-m3 -mthumb
CFLAGS += -ffunction-sections -fdata-sections
ASFLAGS =
LDFLAGS = -nostartfiles -specs=nosys.specs
CPFLAGS =
ODFLAGS = -x --syms
PRFLAGS ?=
# Source files
LINKER_SCRIPT = LPC17xx.ld
OBJS = $(SOURCE:.c=.o)
BUILD_OBJS := $(patsubst %,$(BUILD_DIR)/%,$(SOURCE:.c=.o))
OBJ_DIR := $(dir $(BUILD_OBJS))
INC_PARAMS = $(foreach d, $(INCLUDE_DIR), -I$d)
print-% : ; #echo $* = $($*)
.PHONY: all size clean nuke
all: $(TARGET).bin $(TARGET).hex
isp: $(TARGET).bin
# #./quiet $< cp $^ $(MBED_VOLUME)/
size: $(TARGET).elf
#$(SIZE) $<
%.hex: %.elf
$Q $(CP) $(CPFLAGS) -O ihex $< $*.hex
%.bin: %.elf
$Q $(CP) $(CPFLAGS) -O binary $< $*.bin
$(TARGET).elf: $(OBJS)
#touch $(#:.elf=.map)
$Q $(LD) -Xlinker -Map $(#:.elf=.map) $(LDFLAGS) -T $(LINKER_SCRIPT) $(BUILD_DIR)/$^ -o $#
$Q $(OD) $(ODFLAGS) $# > $(#:.elf=.dump)
#$(SIZE) $#
$(OBJS): %.o: %.c
mkdir -p $(dir $(BUILD_OBJS))
$Q $(CC) $(CFLAGS) $(INC_PARAMS) -c $< -o $(BUILD_DIR)/$#
.PHONY: clean
clean: CRUFT=$(shell find . -name '*.o' -o -name '*.d')
clean: ; rm -f $(CRUFT); rm -f *.elf *.hex *.bin *.dump *.map; rm -rf $(BUILD_DIR)/*
nuke: clean
-rm -f *.hex *.bin *.dump *.map
The issue is in line:
$Q $(LD) -Xlinker -Map $(#:.elf=.map) $(LDFLAGS) -T $(LINKER_SCRIPT) $(BUILD_DIR)/$^ -o $#
For which the output is:
arm-none-eabi-gcc -Xlinker -Map demo.map -nostartfiles -specs=nosys.specs -T LPC17xx.ld build/objs/modbus/ascii/mbascii.o modbus/functions/mbfunccoils.o modbus/functions/mbfuncdiag.o modbus/functions/mbfuncdisc.o modbus/functions/mbfuncholding.o modbus/functions/mbfuncinput.o modbus/functions/mbfuncother.o modbus/functions/mbutils.o modbus/port/port.o modbus/port/portevent.o modbus/port/portserial.o modbus/port/porttimer.o modbus/rtu/mbcrc.o modbus/rtu/mbrtu.o modbus/mb.o demo.o startup_LPC17xx.o system_LPC17xx.o -o demo.elf
The output command picks only the first object file from correct directory under build/objs.
Rest object file path doesn't have build/objs/.
Please anyone help me resolve this issue?
You are violating Mad Scientist's second rule of makefiles, and inviting more problems than you know.
Look at these rules (simplified):
$(TARGET).elf: $(OBJS)
$(LD) $(BUILD_DIR)/$^ -o $#
$(OBJS): %.o: %.c
$(CC) -c $< -o $(BUILD_DIR)/$#
Suppose the build directory is build/ and the object file is build/foo.o. The target of the second rule is foo.o, but what it actually builds is build/foo.o. Likewise, the first rule claims foo.o as a prerequisite, but it doesn't actually use foo.o, it uses build/foo.o These two errors cancel each other out, in a sense; Make succeeds in building the main target. But as you have found, it has trouble if there is more than one object file, because if the prerequisite list is foo.o bar.o, then
`$(BUILD_DIR)/$^`
expands to
build/foo.o bar.o
Also, Make will run the second rule even if build/foo.o exists and is up to date, and will fail to run it if foo.o exists. The target of a non-PHONY rule should be the name of the file it builds, and a non-PHONY prerequisite should be the name of a file whose existence is relevant:
$(TARGET).elf: $(BUILD_OBJS)
$(LD) $^ -o $#
$(BUILD_OBJS): $(BUILD_DIR)/%.o: %.c
$(CC) -c $< -o $#

Define target for recursive makefiles invocation

What I need: make LAUNCHMAKES target dependent on something in order to not invoke sub makefiles when no source file has been changed.
SUB MakeFile contains collecting of all .cpp files in the dir and stores them to the obj/. Then no src file changed it still invokes and engenders "Entering directory - Leaving directory". I need to get rid of it when there is no need.
I've read about --no-print-directory but it's not the case.
The project's structure:
Project
|----project
| |----dir1
| | |----src1.cpp
| | `----Makefile
| |----dir2
| | |----src2.cpp
| | `----Makefile
`----main.cpp
|----obj
| |----src1.o
| |----src2.o
| `----main.o
|----MakeFile
The code:
release: LAUNCHMAKES obj/main.o
$(CXX) $(CXXFLAGS) main.o src1.o src2.o -o result
LAUNCHMAKES: (?)
for i in $(SUBDIRS); do \
$(MAKE) -C $$i CXXFLAGS=$(CXXFLAGS); \
done
obj/main.o: project/main.cpp
$(CXX) $(CXXFLAGS) -c $< -o $#
Not a solution to your problem but an example of a non-recursive Makefile, just to show you how simple it could be for your simple example project:
SUBDIRS := dir1 dir2
OBJDIR := obj
SRCS := $(shell find . -type f -name '*.cpp')
OBJS := $(patsubst %.cpp,$(OBJDIR)/%.o,$(notdir $(SRCS)))
VPATH := $(SUBDIRS)
.PHONY: release clean
release: result
result: $(OBJS)
$(CXX) $(LDFLAGS) $^ -o $#
$(OBJDIR)/%.o: %.cpp
$(CXX) $(CXXFLAGS) -c $< -o $#
clean:
rm -f $(OBJDIR)/*.o result
Demo:
$ make
g++ -c dir2/src2.cpp -o obj/src2.o
g++ -c dir1/src1.cpp -o obj/src1.o
g++ -c main.cpp -o obj/main.o
g++ obj/src2.o obj/src1.o obj/main.o -o result

How to ensure `make` rebuilds all objects after deletion of a file

Consider the following Makefile skeleton:
HEADERS := $(shell find . -name "*.h" | sort)
SOURCES := $(shell find . -name "*.c" | sort)
OBJECTS := $(patsubst %.c, %.o, $(SOURCES))
executable: $(OBJECTS)
$(CC) $(CFLAGS) -o $# $(OBJECTS)
%.o: %.c
rm -f $#
$(CC) $(CFLAGS) -c -o $# $< || { rm -f $#; exit 1; }
$(OBJECTS): Makefile $(HEADERS)
These rules ensure that OBJECTS and executable are recompiled if any of the following files is modified:
Declaration files (*.h)
Implementation files (*.c)
The Makefile itself
This works very nicely. It also covers the case of adding a new source code file to a directory of the project (assuming the new file wasn't added using cp -a or mv). The case that isn't covered is the deletion of a file.
Recompilation on deletion is useful because it catches leftovers in the remaining source code and because it removes superfluous data from the executable.
What is a succinct and efficient way of ensuring the make command rebuilds all objects after a source code file is deleted from the project?
The answer can use any common Linux command via $(shell ...).
The reply provided by MadScientist is very good (particularly regarting the use of a modern auto-generated dependencies method).
If the modern auto-generated dependencies method is not an option, an alternative to using echo and cmp would be to have the dependencies handled by make, using include and $(file ...) (note the extra dependency for $(OBJECTS)):
HEADERS := $(shell find . -name "*.h" | sort)
SOURCES := $(shell find . -name "*.c" | sort)
OBJECTS := $(patsubst %.c, %.o, $(SOURCES))
executable: $(OBJECTS)
$(CC) $(CFLAGS) -o $# $(OBJECTS)
%.o: %.c
rm -f $#
$(CC) $(CFLAGS) -c -o $# $< || { rm -f $#; exit 1; }
$(OBJECTS): Makefile $(HEADERS) deps.mk
include deps.mk
deps.mk: $(HEADERS) $(SOURCES)
$(file >$#,$#: $(HEADERS) $(SOURCES))
$(file >>$#,$(HEADERS) $(SOURCES):)
That is only one example and any combination of behavior is possible. For instance if your requirements were:
recompile everything when
a header file is created or modified; or
a header or source file is deleted
otherwise, recompile only the added or modified source files - if any
you could capture the list of deleted files in a MISSING using the filter-out function:
HEADERS := $(shell find . -name "*.h" | sort)
SOURCES := $(shell find . -name "*.c" | sort)
OBJECTS := $(patsubst %.c, %.o, $(SOURCES))
executable: $(OBJECTS)
$(CC) $(CFLAGS) -o $# $(OBJECTS)
%.o: %.c
rm -f $#
$(CC) $(CFLAGS) -c -o $# $< || { rm -f $#; exit 1; }
include missing.mk
ifneq (,$(MISSING))
$(MISSING):
endif
$(OBJECTS): Makefile $(HEADERS) $(MISSING)
missing.mk: $(MISSING) $(HEADERS) $(SOURCES)
$(file >$#,MISSING=$$(filter-out $$(HEADERS) $$(SOURCES),$(HEADERS) $(SOURCES)))
You can create a listfile (name listfile) which contains the list of headers and C sources. If you delete a file from source tree you'll need to re-create this listfile. The listfile should be dependency in Makefile.
HEADERS := $(shell find . -name "*.h" | sort)
SOURCES := $(shell find . -name "*.c" | sort)
OBJECTS := $(patsubst %.c, %.o, $(SOURCES))
executable: $(OBJECTS)
$(CC) $(CFLAGS) -o $# $(OBJECTS)
%.o: %.c
rm -f $#
$(CC) $(CFLAGS) -c -o $# $< || { rm -f $#; exit 1; }
genlist:
find . -name \*.c -or -name \*.h > listfile
$(OBJECTS): Makefile $(HEADERS) listfile
.PHONY: genlist
After deletion you should run make genlist.
Of course you can expand this idea: you'll create a listfile and every make will generate a temporary listfile (for example use mktemp) and compares to the "official" listfile. If they differ will overwrite the "official" listfile - and don't have to run make genlist after deletion.
Everybody suggests to generate auxiliary makefile. I quite support these suggestions. However I'd prefer to keep isolated generation of this makefile. Here is solution to keep it in a single $(shell ...) command:
THIS_MAKEFILE := $(lastword $(MAKEFILE_LIST))
$(if $(shell find -name '*.h' -or -name '*.c' | \
awk '\
/.*\.h$$/ { h=h " " $$0 } \
/.*\.c$$/ { c=c " " $$0 } \
END { \
print "HEADERS :=" h; \
print "SOURCES :=" c \
}' > $(THIS_MAKEFILE).include.new && \
diff -q $(THIS_MAKEFILE).include.new $(THIS_MAKEFILE).include 2> /dev/null || mv $(THIS_MAKEFILE).include.new $(THIS_MAKEFILE).include),)
include $(THIS_MAKEFILE).include
OBJECTS := $(patsubst %.c, %.o, $(SOURCES))
executable: $(OBJECTS); $(CC) $(CFLAGS) -o $# $(OBJECTS)
%.o: %.c; rm -f $# && $(CC) $(CFLAGS) -c -o $# $< || { rm -f $#; exit 1; }
$(OBJECTS): $(THIS_MAKEFILE) $(THIS_MAKEFILE).include $(HEADERS)
$(shell) is wrapped in $(if) to suppress any output from it.
This solution has one $(shell) call instead of two and doesn't depend on relatively new $(file) GNU Make function.
Good for small project, but doesn't scale well.
One way to handle this would be to use a modern auto-generated dependencies method such as the one described here. These methods have built-in properties that handle deleted files correctly.
If you don't want to do that, then something similar to what #uzsolt suggests is what you need to do, but you have to play a trick if you want to avoid the need to run make genlist explicitly before make realizes a file is missing:
HEADERS := $(shell find . -name "*.h" | sort)
SOURCES := $(shell find . -name "*.c" | sort)
OBJECTS := $(patsubst %.c, %.o, $(SOURCES))
executable: $(OBJECTS)
$(CC) $(CFLAGS) -o $# $(OBJECTS)
%.o: %.c
rm -f $#
$(CC) $(CFLAGS) -c -o $# $< || { rm -f $#; exit 1; }
$(OBJECTS): Makefile $(HEADERS) sourcelist
sourcelist: FORCE
#for f in $(SOURCES) $(HEADERS); do echo "$$f"; done > $#.tmp
#[ `comm -23 $# $#.tmp | wc -l` -eq 0 ] || mv $#.tmp $#
FORCE:
The idea here is that you compare the old list to the new list and only modify the list if the old list contains something that the new list doesn't. This ensures that unless something is deleted the timestamp for sourcelist doesn't change, and so it won't force the object files to be out of date.

Resources