Using Makefile with subfolders - makefile

I have the following directory structure.
Project/
├── bin/
├── src/
│ ├── main.c
│ ├── util/
│ ├── util.c
│ ├── util.h
├── obj/
├── .depend/
All my source code are in the src folder. In the src root is my main.c file; which includes other files that are on the same level that he (or within a same level folder). I have a Makefile below that works well for all files in the same level of main.c but does not work on files in a subfolder within src
How change my Makefile to allow subfolder within the src folder?
CC := gcc
CFLAGS := -Wall -Wextra
BINDIR := bin
OBJDIR := obj
SRCDIR := src
DEPDIR := .depend
SOURCES := $(wildcard $(SRCDIR)/*.c)
OBJECTS := $(patsubst $(SRCDIR)/%.c, $(OBJDIR)/%.o, $(SOURCES))
DEPENDS := $(patsubst $(SRCDIR)/%.c, $(DEPDIR)/%.d, $(SOURCES))
$(BINDIR)/app: $(OBJECTS) | $(BINDIR)
#$(CC) -o $# $^
-include $(DEPENDS)
$(OBJDIR)/%.o: $(SRCDIR)/%.c | $(OBJDIR)
#$(CC) $(CFLAGS) -c -o $# $<
$(DEPDIR)/%.d: $(SRCDIR)/%.c | $(DEPDIR)
#$(CC) -MM -MG $< | sed 's!^\(.\+\).o:!$(DEPDIR)/\1.d $(OBJDIR)/\1.o:!' > $#
$(DEPDIR) $(BINDIR) $(OBJDIR):
#mkdir $#
clean:
#rm -rf $(BINDIR)/*
#rm -rf $(OBJDIR)/*
.PHONY: clean
EDIT: the .o and .d files do not need to respect the original design of the structure. And I'm using Windows (MinGW)

First, you'll have to change your SOURCES to recursively find the sources. This can be done in pure make:
subdirs = $(filter-out $1,$(sort $(dir $(wildcard $1*/))))
rfind = $(wildcard $1$2) $(foreach d,$(call subdirs,$1),$(call rfind,$d,$2))
SOURCES := $(call rfind,$(SRCDIR)/,*.c)
Everything else will work, except for directory creation. First, change your prerequisites to use $(#D) with secondary expansion:
.SECONDEXPANSION:
$(OBJDIR)/%.o: $(SRCDIR)/%.c | $$(#D)
...
$(DEPDIR)/%.d: $(SRCDIR)/%.c | $$(#D)
...
Then, change your directory creation rule to include all the directories:
$(BINDIR) $(patsubst %/,%,$(sort $(dir $(OBJECTS) $(DEPENDS)))):
#mkdir -p $#
Like the recursive find, it uses sort to deduplicate the directories (otherwise make will warn) and strips the trailing slash (because $(#D) won't have a trailing slash). Note that -p is needed to avoid issues with order and with directories only containing other directories and no sources.

try to set your sources like this:
SOURCES := $(shell find $(SRCDIR) -type f -name "*.c")
if it does not work right a way, try to debug it by just running in your command line
find src -type f -name "*.c"
and see if it outputs the correct list of files and with correct relative path, adjust accordingly.
Note, that this approach works only in unix-like environment, if you are using MinGW from MSYS or Cygwin environment it should work.

Related

Write generic Makefile rule for subdirectories

I have a project with a directory tree that looks like this:
.
├── modules
│   ├── mod1
│   │   └── mod1.f90
│   ├── mod2
│   │   └── mod2.f90
│   └── mod.f90
└── src
└── main.f90
├── bin
└── Makefile
main.f90 uses all the modules found in modules and in the 'submodules' that are mod1/ and mod2/.
A simple Makefile I can write to compile the project is something like:
F90 = gfortran
FLAGS = -g -I$(BINDIR)
MODFLAGS = -J$(BINDIR)
BINDIR = bin
SRCDIR = src
MODDIR = modules
SMODDIR = $(dir $(wildcard $(MODDIR)/*/.))
MODFILES = $(wildcard $(MODDIR)/*f90)
MODOBJ = $(join $(addsuffix $(MODFROMBIN)/, $(dir $(MODFILES))), \
$(notdir $(MODFILES:.f90=.o)))
SMODFILES = $(foreach smoddir, $(SMODDIR), $(wildcard $(smoddir)*f90))
SMODOBJ = $(join $(addsuffix $(SMODFROMBIN)/, $(dir $(SMODFILES))), \
$(notdir $(SMODFILES:.f90=.o)))
SRCFILES = $(wildcard $(SRCDIR)/*f90)
SRCOBJ = $(join $(addsuffix $(SRCFROMBIN)/, $(dir $(SRCFILES))), \
$(notdir $(SRCFILES:.f90=.o)))
ALLOBJ = $(MODOBJ) $(SMODOBJ) $(SRCOBJ)
BINOBJ = $(addprefix $(BINDIR)/, $(sort $(notdir $(ALLOBJ))))
all: main
$(MODDIR)/../bin/%.o: $(MODDIR)/%.f90
$(F90) $(FLAGS) -c $^ -o $# $(MODFLAGS)
modules/mod1/../../bin/%.o: modules/mod1/%.f90
$(F90) $(FLAGS) -c $^ -o $# $(MODFLAGS)
modules/mod2/../../bin/%.o: modules/mod2/%.f90
$(F90) $(FLAGS) -c $^ -o $# $(MODFLAGS)
$(SRCDIR)/../bin/%.o: $(SRCDIR)/%.f90
$(F90) $(FLAGS) -c $^ -o $#
main: $(ALLOBJ)
$(F90) $(FLAGS) -o ./bin/main $(BINOBJ)
clean:
#rm bin/*.o bin/*.mod
But now I want to write a generic rule to be able to compile all modules located in directories that are inside modules directory (the code I am working on have more than two submodules and I am not willing to write as many rules as I have subdirectories).
My first try was to write something like this:
$(SMODDIR)/../../%.o: $(SMODDIR)/%.f90
$(F90) $(FLAGS) -c $^ -o $# $(MODFLAGS)
but it fails; from what I understand, it will put all the subdirectories paths and I would actually end up with a rule that would looks like this:
modules/mod1/../../bin/ modules/mod2/../../bin/%.o: modules/mod1/ modules/mod2/%.f90
that indeed looks funny.
As shown in the original Makefile, it is possible to retrieve specificaly with
SMODDIR = $(dir $(wildcard $(MODDIR)/*/.))
SMODFILES = $(foreach smoddir, $(SMODDIR), $(wildcard $(smoddir)*f90))
so I guess it may be possible to use something similar to have more generic rules. I couldn't see however how to use such a syntax to write a rule that makes sense.
Any help would be appreciated!
You have two choices to avoid writing lots of rules.
One is, you can use VPATH and put all your source directories in it, something like this:
VPATH := $(MODDIR) $(SMODDIR) $(SRCDIR)
$(BINDIR)/%.o: %.f90
$(F90) $(FLAGS) -c $< -o $# $(MODFLAGS)
Or you can put all your object files into equivalent subdirectories of $(BINDIR), something like this:
MODOBJ = $(MODFILES:%.f90=$(BINDIR)/%.o)
SMODOBJ = $(SMODFILES:%.f90=$(BINDIR)/%.o)
SRCOBJ = $(SRCFILES:%.f90=$(BINDIR)/%.o)
$(BINDIR)/%.o: %.f90
#mkdir -p $(#D)
$(F90) $(FLAGS) -c $< -o $# $(MODFLAGS)

Makefile file matching sources from different subfolders into single build folder

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/ ?
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.
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.
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.

Generic Makefile build directory error

I have the following directory structure for a dummy C project.
.
├── inc
│   ├── getmsg.c
│   └── getmsg.h
├── makefile
└── src
└── main.c
My current generic Makefile is below,
TARGET = main
# finds all .c files, inc/getmsg.c src/main.c
SOURCES := $(shell find * -name *.c)
# converts all .c files to .o files, inc/getmsg.o src/main.o
OBJECTS := $(SOURCES:.c=.o)
# directories that contain .h files for gcc -I flag, inc/
HEADERS := $(dir $(shell find * -name *.h))
CC = gcc
CFLAGS = -Wall -std=c99 -iquote "$(HEADERS)"
all: $(TARGET)
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $#
$(TARGET): $(OBJECTS)
$(CC) -o $# $^
clean:
rm -rf $(shell find * -name *.o) $(TARGET)
This all compiles fine however it just dumps the .o files into the same directory as its corresponding .c file.
What I would like to do is have all object files put into a build directory. To do this I change the OBJECTS to OBJECTS := $(patsubst %,build/%,$(notdir $(SOURCES:.c=.o))) which lists the files build/getmsg.o build/main.o. Then I set the %.o target to build/%.o: %.c.
This however returns No rule to make target 'build/getmsg.o'. So the make file is unable to build the .o files. What am I missing here?
Try changing
%.o: %.c
to
build/%.o: %.c

Using GNU Make with subdirectories

I was wondering what different approaches of using Make in a project with subdirectories exist, and what are their advantages/drawbacks, but could never see a good summary or cookbook.
I have seen in my researches mainly the "recursive" and "single makefile" approaches, but are there others ?
I also assume that there is not only one "recursive" or "single makefile" approaches but several, so could somebody sum it up ?
For my particular case, I would like a directory architecture looking like this:
.
├── build
│   ├── *.d
│   ├── *.o
| ├── subdir1
| │ ├── *.d
| │ └── *.o
| └── subdir2
| ├── *.d
| ├── *.o
| └── subdir3
| ├── *.d
| └── *.o
├── include
│   ├── *.h
│   └── *.h
├── Makefile
└── src
├── *.c
├── *.h
├── subdir1
│   ├── *.c
│   └── *.h
└── subdir2
├── *.c
├── *.h
└── subdir3
├── *.c
└── *.h
Which solution should I choose ? Possibly one which would allow source files with the same name ?
Your project setup is really basic, so should be your Makefile:
SRC_DIR := src
BLD_DIR := build
SRC := $(shell find $(SRC_DIR) -name "*.c")
OBJ := $(SRC:$(SRC_DIR)/%.c=$(BLD_DIR)/%.o)
DEP := $(OBJ:.o=.d)
CPPFLAGS := -MMD -MP # enable auto-dependency generation
CFLAGS := -Wall -W -pedantic
.PHONY: all clean
all: $(OBJ)
clean:
$(RM) -r $(BLD_DIR)
.SECONDEXPANSION:
$(BLD_DIR)/%.o: $(SRC_DIR)/%.c | $$(#D)/ # First check that the destination directory exists
$(CC) $(CPPFLAGS) $(CFLAGS) -o $# -c $<
%/:
mkdir -p $* # -p flag necessary for recursive directory creation
ifeq "$(MAKECMDGOALS)" ""
-include $(DEP)
endif
The idea here is to list source files recursively using the find command, to supply make with the appropriate pattern rule to compile in the right place and pass the right preprocessor file to your compiler to enable auto-dependency generation.
Tested with GNU Make 4.1 under Windows 8.1 with the GIT Bash shell and the following directory structure:
.
├── Makefile
└── src
├── test.c
├── test1.c
└── subdir1
└── test.c
After reading Recursive Make Considered Harmful, I figured a quite simple and modular way to achieve this, by having files in all subdirectories that would include each other and be included in the main makefile:
CXX := gcc
SRCDIR := src
OBJDIR := build
# These lines are needed to set immediate evaluation for
# these variables, instead of deferred evaluation which is unsuitable.
SRCS :=
SUBDIRS :=
CFLAGS :=
LDFLAGS :=
include $(SRCDIR)/module.mk
OBJS := $(addprefix $(OBJDIR)/, $(SRCS:.c=.o))
SRCS := $(addprefix $(SRCDIR)/, $(SRCS))
DEPS := $(OBJS:.o=.d)
TMPS := $(OBJS) $(OBJS:.o=.d)
CFLAGS += -MD
debug: CFLAGS += -g -g3 -ggdb
CFLAGS += $(addprefix -I./$(SRCDIR)/, $(SUBDIRS))
LDFLAGS += -lsomelib
debug: LDFLAGS += -g -g3 -ggdb
NAME := yolo
all: $(NAME)
debug: re
-include $(DEPS)
$(OBJDIR)/%.o: $(SRCDIR)/%.c
$(COMPILE.c) $(OUTPUT_OPTION) $<
$(NAME): $(OBJS)
#$(CXX) $(OBJS) -o $(NAME) $(LDFLAGS)
$(OBJS): | $(OBJDIR)
$(OBJDIR):
#mkdir -p $(OBJDIR)
#for dir in $(SUBDIRS); \
do \
mkdir -p $(OBJDIR)/$$dir; \
done
clean:
rm -rf $(TMPS)
fclean: clean
rm -rf $(NAME)
rm -rf $(OBJDIR)
re: fclean all
.PHONY: all clean fclean re
And in every subdirectory, a module.mk file (I could have named it anything, but this seemde cool).
For src:
SRCS := main.c file1.c file2.c
SUBDIRS += subdir1 subdir2
include $(SRCDIR)/subdir1/module.mk
include $(SRCDIR)/subdir2/module.mk
For a level 1 subdirectory:
THIS_DIR_L0 := subdir1
MOD_SRC := file3.c file4.c
SRCS += $(addprefix $(THIS_DIR_L0)/, $(MOD_SRC))
SUBDIRS += $(THIS_DIR_L0)/subdir3
include $(SRCDIR)/$(THIS_DIR_L0)/subdir3/module.mk
And for a level 2 subdir (one deeper):
THIS_DIR_L1 := subdir3
MOD_SRC := file5.c file6.c
SRCS += $(addprefix $(THIS_DIR_L0)/$(THIS_DIR_L1)/, $(MOD_SRC))
And so on...
This is quite simple to set up, I find it very modular and it does not use recursive makefiles. It would not be complicated to make librairies and stuff inside your directory structure either.
Anybody having a better idea please tell me.

How to make makefile find target in subdirectory makefile

How to make top level Makefile to call all targets in subdirectory Makefile ?
My folder structure is like this
/Makefile
/src/Makefile
I code all targets in /src/Makefile, but now I want to write another /Makefile to ease my work. How to write this top level Makefile ?
You could write in your TOP makefile use
all:
#$(MAKE) -C src
and if you do NOT use makefile in sub dir, for example, you use somename.mk, you could use
all:
#$(MAKE) -C src -f somename.mk
I show you my example, my DIR looks like:
TOPDIR-- Makefile
|
|-- debug
| |-- debug.c
| |-- debug.h
| |-- debug.mk
| |-- instrument.c
| `-- uart_print.c
|-- driver
| |-- driver.c
| |-- driver_ddi.c
| |-- driver_ddi.h
| |-- driver.h
| `-- driver.mk
|-- include
| `-- common.h
|-- Makefile
|-- mw
| |-- manager.c
| `-- mw.mk
|-- root
| |-- main.c
| `-- root.mk
and my TOP makefile looks like:
MAKE_DIR = $(PWD)
ROOT_DIR := $(MAKE_DIR)/root
DRV_DIR := $(MAKE_DIR)/driver
INCLUDE_DIR := $(MAKE_DIR)/include
DEBUG_DIR := $(MAKE_DIR)/debug
INC_SRCH_PATH :=
INC_SRCH_PATH += -I$(ROOT_DIR)
INC_SRCH_PATH += -I$(DRV_DIR)
INC_SRCH_PATH += -I$(INCLUDE_DIR)
INC_SRCH_PATH += -I$(DEBUG_DIR)
LIB_SRCH_PATH :=
LIB_SRCH_PATH += -L$(MAKE_DIR)/libs
COLOR_ON = color
COLOR_OFF =
CC = $(COLOR_ON)gcc
#CC = $(COLOR_OFF)gcc
LD = ld
LINT = splint
LIBS := -ldriver -ldebug -lmw -lm -lpthread
CFLAGS :=
CFLAGS += $(INC_SRCH_PATH) $(LIB_SRCH_PATH)
CFLAGS += -Wall -O -ggdb -Wstrict-prototypes -Wno-pointer-sign -finstrument-functions -fdump-rtl-expand
CFLAGS += -DDEBUG -D_REENTRANT
LDFLAGS :=
export MAKE_DIR CC LD CFLAGS LDFLAGS LIBS LINT INC_SRCH_PATH
all:
#$(MAKE) -C debug -f debug.mk
#$(MAKE) -C driver -f driver.mk
#$(MAKE) -C mw -f mw.mk
#$(MAKE) -C root -f root.mk
.PHONY: clean
clean:
#$(MAKE) -C debug -f debug.mk clean
#$(MAKE) -C driver -f driver.mk clean
#$(MAKE) -C mw -f mw.mk clean
#$(MAKE) -C root -f root.mk clean
.PHONY: lint
lint:
$(MAKE) -C debug -f debug.mk lint
it will call sub DIR *.mk during the compile. The sub DIR makefile, I just write a simple example for you reference:
LIB = $(MAKE_DIR)/libs/yourmodulename.a
SRCS = $(wildcard *.c)
OBJS = $(patsubst %.c, %.o, $(SRCS))
$(LIB): $(OBJS)
#mkdir -p ../libs
#$(AR) cr $# $^
#echo " Archive $(notdir $#)"
$(OBJS): $(SRCS)
#$(CC) $(CFLAGS) -c $^
#echo " CC $(OBJS)"
.PHONY: clean
clean:
#$(RM) -f $(LIB) $(OBJS)
#$(RM) -f *.expand
#echo " Remove Objects: $(OBJS)"
#echo " Remove Libraries: $(notdir $(LIB))"
.PHONY: lint
lint:
$(LINT) $(INC_SRCH_PATH) $(SRCS)
for the makefile, which generate the target file is little bit different, because I use the sub makefile to generate LIB file, and I use a root.mk to generate target:
PROG = ../prog/DEMO
SRCS = $(wildcard *.c)
OBJS = $(patsubst %.c, %.o, $(SRCS))
$(PROG): $(SRCS)
#mkdir -p ../prog
#$(CC) $^ $(CFLAGS) -Wl,-Map=$(PROG).map $(LIBS) -o $#
#echo " Generate Program $(notdir $(PROG)) from $^"
.PHONY: clean
clean:
#$(RM) -f $(OBJS) $(PROG)
#$(RM) -f *.expand
#$(RM) -rf ../prog ../libs
#echo " Remove Objects: $(OBJS)"
#echo " Remove Libraries: $(notdir $(PROG))"
Beware, note there are, at least, two ways of doing this.
GNU Changedir Option
One way is using a specific feature of GNU Make, the -C option allow to change your compile directory and reach another one:
all:
make -C dir
The make manual says:
-C dir, --directory=dir
Change to directory dir before reading the makefiles or doing anything
else. If multiple -C options are specified, each is interpreted relative
to the previous one: -C / -C etc is equivalent to -C /etc. This is
typically used with recursive invocations of make.
You can also combine this option by calling a specific target inside the targeted directory. For example, the following target will get into the src/ directory and call make with the clean target:
clean:
#rm -f *.o
make -C src/ clean
POSIX Way
The problem with the GNU way of doing is that it only works with GNU Make, and not the standard Make. If you may use another Make (for whatever reason), you better consider doing it in a more POSIX way.
In POSIX Make, you have to rely more on the cd command, like this:
all:
cd src/ && make
Note that, I used && and not ;. It is quite important to avoid infinite recursive calls to make. Indeed, cmd1 ; cmd2 will execute sequentially cmd1 and cmd2 whatever is the result of each command, where cmd1 && cmd2 will execute sequentially cmd1, and cmd2 will be executed only if cmd1 returned an EXIT_SUCCESS. In our case, imagine that the first cd failed because the directory has been removed. Then, the initial makefile will be executed again and again in an infinite recursive loop.
Anyway, this POSIX manner, is the more robust way of descending in subdirectories and execute other Makefile. I would recommend you to use it better than relying on an option linked to GNU Make.
And if you want to do multiple dirs easily:
SUBDIRS=dir1 dir2
all::
#echo make all
$(foreach var,$(SUBDIRS),echo $(var): ; cd $(var)/ && make $# && cd ..;)
clean:
#echo make clean
$(foreach var,$(SUBDIRS),echo $(var): ; cd $(var)/ && make $# && cd ..;)

Resources