Is it possible to have multiple prerequisite patterns in the same target in a Makefile? - makefile

I have two targets like this
$(OBJ1): $(BUILDDIR)/%.o: $(BUILDROOT)/proto/a/%.pb.cc
$(OBJ2): $(BUILDDIR)/%.o: $(BUILDROOT)/proto/a/b/%.pb.cc
Is it possible to combine these two into the same target somehow?

It is possible but you will need advanced make features (macros):
SRC := $(shell find $(BUILDROOT)/proto -type f -name '*.cc')
OBJ := $(addprefix $(BUILDDIR)/,$(patsubst %.cc,%.o,$(notdir $(SRC))))
compile: $(OBJ)
# $(1) is the cc source file
define MY_rule
$$(BUILDDIR)/$$(patsubst %.cc,%.o,$$(notdir $(1))): $(1)
$$(CXX) -c $$(CXXFLAGS) -o $$# $$<
endef
$(foreach f,$(SRC),$(eval $(call MY_rule,$(f))))
Demo:
$ ls -R proto
proto:
dira
proto/dira:
a.cc dirb
proto/dira/dirb:
b.cc
$ make BUILDROOT=. BUILDDIR=build compile
g++ -c -o build/a.o proto/dira/a.cc
g++ -c -o build/b.o proto/dira/dirb/b.cc
Please have a look at the section about the eval function of the GNU make manual for a complete explanation.
Late update: one comment about your other (now deleted) similar question suggested to use the vpath directive. It is tricky too and adds an important constraint which is that all source files must have different basenames. For completeness, and assuming the constraint is satisfied, here is another vpath-based solution:
vpath <pattern> dira dirb dirc:...
tells make that when searching for a file that matches <pattern>, it must explore the listed directories. So, let us:
Compute the basenames of all source files and the corresponding object files:
SRC := $(notdir $(shell find $(BUILDROOT)/proto -type f -name '*.cc'))
OBJ := $(addprefix $(BUILDDIR)/,$(patsubst %.cc,%.o,$(SRC)))
Get the list of all directories in $(BUILDROOT)/proto:
DIR := $(shell find $(BUILDROOT)/proto -type d)
Now, we are ready to use the vpath directive:
vpath %.cc $(DIR)
That's it. All in all, the following should work:
SRC := $(notdir $(shell find $(BUILDROOT)/proto -type f -name '*.cc'))
DIR := $(shell find $(BUILDROOT)/proto -type d)
OBJ := $(addprefix $(BUILDDIR)/,$(patsubst %.cc,%.o,$(SRC)))
vpath %.cc $(DIR)
compile: $(OBJ)
$(BUILDDIR)/%.o: %.cc
$(CXX) -c $(CXXFLAGS) -o $# $<

Related

Compile C source files from tree into build directory with make

I have a Makefile that looks similar to this condensed version:
.PHONY: all
CC = gcc -O3 -fPIC
SRC = $(shell find src -type f -name '*.c')
OBJ = $(addprefix build/, $(notdir $(SRC:.c=.o))
TGT = build/prog
all: $(TGT)
$(TGT): $(OBJ)
$(CC) $+ -o $#
build/%.o: src/a/%.c
$(CC) -c $+ -o $#
build/%.o: src/b/%.c
$(CC) -c $+ -o $#
build/%.o: src/c/d/%.c
$(CC) -c $+ -o $#
I would like to combine the build/%.o rules into one rule because the real makefile has like twenty of these for various paths in the src directory. But whatever I tried nothing works. I'm sure there must be a solution, please enlighten me how this can be done.
As stated in the comment you can make use of GNU make's vpath directive...
.PHONY: all
CC = gcc -O3 -fPIC
SRC = $(shell find src -type f -name '*.c')
OBJ = $(addprefix build/, $(notdir $(SRC:.c=.o))
TGT = build/prog
all: $(TGT)
$(TGT): $(OBJ)
$(CC) $+ -o $#
# Use vpath to give make a colon (or space) separated list
# of directories in which to look for .c files.
#
vpath %.c src/a:src/b:src/c/d
build/%.o: %.c
$(CC) -c $+ -o $#
Since you're using find to locate the source files you could go a step further and use the results of that find to generate the vpath...
.PHONY: all
CC = gcc -O3 -fPIC
SRC = $(shell find src -type f -name '*.c')
OBJ = $(addprefix build/, $(notdir $(SRC:.c=.o))
TGT = build/prog
all: $(TGT)
$(TGT): $(OBJ)
$(CC) $+ -o $#
# Use vpath to give make a colon (or space) separated list
# of directories in which to look for .c files. Make use of
# the $(SRC) variable to generate the list of paths automatically.
#
SRC_DIRS := $(sort $(dir $(SRC)))
vpath %.c $(SRC_DIRS)
build/%.o: %.c
$(CC) -c $+ -o $#

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.

makefile how to output to separate build directory

I have this directory structure:
makefile
src
foo.c
build
My goal is simply to build foo.c and output the build files to the build directory.
I have the following makefile:
SRCS_DIR := ./src
BUILD_DIR := ./build
SRCS := $(shell find $(SRCS_DIR) -name "*.c")
OBJS := $(subst $(SRCS_DIR),$(BUILD_DIR),$(SRCS))
OBJS := $(OBJS:.c=.o)
test.exe: $(OBJS)
gcc $(OBJS) -o $#
%.o: %.c
gcc -c $< -o $#
The problem is the pattern rule. One of the object files is build/foo.o. The problem is that %.c gets turned into build/foo.c, which doesn't exist. What I want %.c to be is src/foo.c instead, but I have no idea how to do that.
The stem of the pattern must match exactly. So if you want a pattern that will put things into a different directory, you have to modify the pattern so that the non-matching parts are not part of the stem. So you can write:
build/%.o : src/%.o
gcc -c $< -o $#
so that the % matches only the common string.

Combine C and C++ in Makefile

I have the following makefile stub. As you can see, I'm trying to pass all .cpp and .c files to the same command line. But I get asdf.c:1: *** missing separator. Stop. I've verified that there are tabs instead of spaces. The error only occurs when I try to combine the C++ and C. Can I make it work?
SRC := $(shell find . -name "*.cpp") $(shell find . -name "*.c")
OBJ := boot.o $(SRC:.cpp=.o) $(SRC:.c=.o)
%.o: %.cpp %.c
$(CXX) -c $(CXXFLAGS) $(CPPFLAGS) $< -o $#
The error only seems to be when I use a .c file. For example:
SRC := $(wildcard *.c)
OBJ := boot.o $(SRC:.c=.o)
%.o: %.c
$(CXX) -c $(CXXFLAGS) $(CPPFLAGS) $< -o $#
gives the same error.
or SRC := $(shell find . -name "*.cpp") asdf.c
The makefile works completely fine until the c file shows up. I've verified this file is the problem because touch test.c and then added test.c to SRC doesn't cause an error.
What could my .c file possibly do to cause make an error?

Depend on subdirectory creation in makefile rule

I have a project with sources in the src/ directory and its subdirectories (e.g. src/foo/ and src/bar/), and the objects in the obj directory and the matching subdirectories (e.g. obj/foo/ and obj/bar/).
I use the following (smimplified) Makefile:
SOURCES=$(shell find src/ -type f -name '*.c')
OBJECTS=$(patsubst src/%.c,obj/%.o,$(SOURCES))
all: $(OBJECTS)
obj/%.o: src/%.c
gcc -c $< -o $#
The problem
The problem is that if obj/ or one of its subdirectories doesn't exist, I get the following error:
Fatal error: can't create obj/foo/f1.o: No such file or directory
How can I tell make that %.o files depend on the creation of their containing directory?
What I tried
One solution when there are no subdirectories is to use "order only prerequisites":
$(OBJECTS): | obj
obj:
mkdir $#
But that fixes the problem only with obj/, but not obj/foo and obj/bar/. I thought about using $(#D), but I don't know how to get all this together.
I have also used hidden marker files in each directory, but that's just a hack, and I have also put a mkdir -p just before the GCC command but that also feels hacky. I'd rather avoid using recursive makefiles, if that were a potential solution.
Minimal example
To create a minimal project similar to mine you can run:
mkdir /tmp/makefile-test
cd /tmp/makefile-test
mkdir src/ src/foo/ src/bar/
echo "int main() { return 0; }" > src/main.c
touch src/foo/f1.c src/bar/b1.c src/bar/b2.c
I don't know why you consider adding mkdir -p before each compiler operation to be "hacky"; that's probably what I'd do. However, you can also do it like this if you don't mind all the directories created all the time:
First, you should use := for assigning shell variables, not =. The former is far more efficient. Second, once you have a list of filenames it's easy to compute the list of directories. Try this:
SOURCES := $(shell find src/ -type f -name '*.c')
OBJECTS := $(patsubst src/%.c,obj/%.o,$(SOURCES))
# Compute the obj directories
OBJDIRS := $(sort $(dir $(OBJECTS))
# Create all the obj directories
__dummy := $(shell mkdir -p $(OBJDIRS))
If you really want to have the directory created only when the object is about to be, then you'll have to use second expansion (not tested):
SOURCES := $(shell find src/ -type f -name '*.c')
OBJECTS := $(patsubst src/%.c,obj/%.o,$(SOURCES))
# Compute the obj directories
OBJDIRS := $(sort $(dir $(OBJECTS))
.SECONDEXPANSION:
obj/%.o : src/%.c | $$(#D)
$(CC) -c $< -o $#
$(OBJDIRS):
mkdir -p $#
I'd do it this way:
SOURCES=$(shell find src -type f -name '*.c') # corrected small error
...
obj/%.o: src/%.c
if [ ! -d $(dir $#) ]; then mkdir -p $(dir $#); fi
gcc -c $< -o $#

Resources