Using sed in a makefile; how to escape variables? - makefile

I am writing a generic makefile to build static libraries. It seems to work well so far, except for the line calling sed:
# Generic makefile to build a static library
ARCH = linux
CFLAGS = -O3 -Wall
SOURCES = src
BUILD_DIR = build/$(ARCH)
TARGET = $(BUILD_DIR)/libz.a
CFILES = $(foreach dir,$(SOURCES),$(wildcard $(dir)/*.c))
OBJECTS = $(addprefix $(BUILD_DIR)/,$(CFILES:.c=.o))
# Pull in the dependencies if they exist
# http://scottmcpeak.com/autodepend/autodepend.html
-include $(OBJECTS:.o=.dep)
default: create-dirs $(TARGET)
$(TARGET): $(OBJECTS)
$(AR) -rc $(TARGET) $^
$(BUILD_DIR)/%.o: %.c
$(CC) $(CFLAGS) -c $< -o $#
$(CC) -M $(CFLAGS) $*.c > $(BUILD_DIR)/$*.tmp
sed s/.*:/$(BUILD_DIR)\\/$*.o:/ $(BUILD_DIR)/$*.tmp > $(BUILD_DIR)/$*.dep
#rm $(BUILD_DIR)/$*.tmp
.PHONY: create-dirs
create-dirs:
#for p in $(SOURCES); do mkdir -p $(BUILD_DIR)/$$p; done
.PHONY: clean
clean:
rm -fr $(BUILD_DIR)
sed is used to replace the path/name of the object file with the full path of where the object actually is. e.g. 'src/foo.o:' is replaced with 'build/linux/src/foo.o:' in this example.
$(BUILD_DIR) and $* in the replacement string both contain forward slashes when expanded - how do I pass them to sed?
Note: This might have been answered here before, but I am so far unable to apply those answers to my specific problem!

You can use anything else than forward slashes as separator in sed. E.g. sed s~foo~bar~g
You can use double quotes " (at least in the shell), and variables will still be expanded: echo "Hello $PLANET"

If you want the path to expand into a file you use
sed -i "s/TO_REPLACE/$(subst, /,\/,${PATH_VARIABLE})/g" filename.txt
If your variable PATH_VARIABLE looked like /opt/path/user/home/etc
it will now inflate to:
\ /opt\ /path\ /user\ /home\ /etc
This should allow sed to insert the ' / ' correctly.
-Matt

Related

Wildcard Pattern Matching in Make

Here is my project structure.
| - src
| - boot
| - table.s
| - boot.s
| - machine
| - revb
| - memmap
| - vars.s
| - os
| - utils
| - ...lots here
I am grouping features by folder, and have a special folder for machine specific code, link scripts, anything.
The problem I am having with make is that I can't seem to get the Pattern match to work.
The below runs and builds the .o files.
#This gets repetative as every file needs to have a recipe by itself.
$(TARGET_DIR)/hash.o : $(SRC_DIR)/utils/hash.s machine
#echo "compiling $<"
$(CC) $(CFLAGS) $< -o $#
#echo "assembly dump $#"
$(DUMP) -D $# > $#.list
The below does NOT work. It doesn't run any of the commands.
#if this works that would be perfect every folder/file will be a recipe!
$(TARGET_DIR)/%.o : $(SRC_DIR)/%.s machine
#echo "compiling $<"
$(CC) $(CFLAGS) $< -o $#
#echo "assembly dump $#"
$(DUMP) -D $# > $#.list
Nothing runs at all, For some reason no files seem to match that pattern.
I have also tried this.
# if this works, it would be a bit annoying as this is per feature recipe/target.
$(TARGET_DIR)/%.o : $(SRC_DIR)/boot/%.s machine
#echo "compiling $<"
$(CC) $(CFLAGS) $< -o $#
#echo "assembly dump $#"
$(DUMP) -D $# > $#.list
Edit
full makefile for reference
CFLAGS = -march=rv32i -mabi=ilp32
CC = riscv32-unknown-linux-gnu-as
LINKER = riscv32-unknown-linux-gnu-ld
DUMP = riscv32-unknown-linux-gnu-objdump
COPY = riscv32-unknown-linux-gnu-objcopy
SRC_DIR = src
TARGET_DIR = target
MACHINE_FILES_DIR = machine
TARGET_MACHINE = revb
# vizoros : $(TARGET_DIR)/%.o
# $(LINKER) $(TARGET_DIR)/boot.o $(TARGET_DIR)/table.o $(TARGET_DIR)/os.o $(TARGET_DIR)/hash.o -T $(TARGET_DIR)/memmap -o $(TARGET_DIR)/$#.elf
# $(DUMP) -D $(TARGET_DIR)/$#.elf > $(TARGET_DIR)/$#.list
$(TARGET_DIR)/%.o : $(SRC_DIR)/boot/%.s machine
#echo "compiling $<"
$(CC) $(CFLAGS) $< -o $#
#echo "assembly dump $#"
$(DUMP) -D $# > $#.list
machine: folders
cp -r $(SRC_DIR)/$(MACHINE_FILES_DIR)/$(TARGET_MACHINE)/. $(TARGET_DIR)
folders:
mkdir -p $(TARGET_DIR)
.phony: clean
clean:
rm -rf $(TARGET_DIR)
As with your other question, you are expecting too much fancy capability from make. Make is a very simple tool. It will not go looking around your directories for files that could be built. It will build only exactly what you ask it to build. It will not infer matching files based on heuristics: it will match only exact strings.
Make always works backwards: it starts with the final target you ask it to build and finds a rule that can build that. Then it looks at each of the prerequisites of that final target and finds a rule that can build each one of those. Then it looks at any prerequisites of each of the prerequisites of those, etc. Once it has built (or found source files for) all the prerequisites of a target, it builds that target, then walks back up until finally it builds the final target you asked for.
In your makefile above you've commented out the "final target" you want built (vizoros), so of course make will not build it.
I'm just going to give you a makefile that solves your problem... if you want to understand what it does please consult the GNU make manual. Note I haven't actually tried this, I just wrote it here. Also note I omitted the whole machine thing because I don't understand what it's supposed to do and you didn't really define it.
CFLAGS = -march=rv32i -mabi=ilp32
CC = riscv32-unknown-linux-gnu-as
LINKER = riscv32-unknown-linux-gnu-ld
DUMP = riscv32-unknown-linux-gnu-objdump
COPY = riscv32-unknown-linux-gnu-objcopy
SRC_DIR = src
TARGET_DIR = target
MACHINE_FILES_DIR = machine
TARGET_MACHINE = revb
# Find all .s files under SRC_DIR
SRCS := $(shell find $(SRC_DIR) -name \*.s)
# Use VPATH with a list of directories to be searched for
VPATH := $(sort $(dir $(SRCS)))
# Convert all .s files into .o files directly under TARGET_DIR
# First strip off the directory, then convert
OBJS := $(patsubst %.s,$(TARGET_DIR)/%.o,$(notdir $(SRCS)))
# Define the final target and provide all the object files as prerequisites
$(TARGET_DIR)/vizoros.elf : $(OBJS)
mkdir -p $(#D)
$(LINKER) $^ -T $(TARGET_DIR)/memmap -o $#
$(DUMP) -D $# > $(#:.elf=.list)
# Define a pattern rule to build an object file in TARGET_DIR
# The source file will be searched for via VPATH
$(TARGET_DIR)/%.o : %.s
mkdir -p $(#D)
#echo "compiling $<"
$(CC) $(CFLAGS) -c $< -o $#
#echo "assembly dump $#"
$(DUMP) -D $# > $#.list
# It must be .PHONY, not .phony: make, like all POSIX tools, is
# case-sensitive
.PHONY: clean
clean:
rm -rf $(TARGET_DIR)
References:
GNU make manual
= vs := variables
shell function
dir and notdir functions
sort and patsubst functions
VPATH
Substitution references
Automatic variables
Phony targets
Also note you had an error in your compile command; you were missing the -c option which tells the compiler to generate an object file rather than a final executable file.

Create dependency files in Makefile

I am using a Makefile including a rule to create a dependency file. The compiler is GCC.
%.d: %.c
mkdir -p $(#D)
$(CC) $(CFLAGS) $(CPPFLAGS) -M $< | \
sed 's,\($(notdir $*)\.o\) *:,$(dir $#)\1 $#: ,' > $#.tmp
mv $#.tmp $#
I am quite new in Makefile technique and I find it a little bit difficult to understand this rule composed of a mixture of several options.
Can somebody give a simple explanation how this rule works?
Thanks in advance.
%.d: %.c
All files ending in .d can be made using this rule, as long as there is a .c file with the same directory and stem relative to the current directory or one of the vpaths.
mkdir -p $(#D)
Create the topmost and all intermediate directories for $(#D), which is an automatic make variable that expands to the directory part of the current target.
$(CC) $(CFLAGS) $(CPPFLAGS) -M $< | \
Invoke the C compiler on the first prerequisite (whatever.c) and tell it to output a list of make dependencies to the standard output. Pipe this output to
sed 's,\($(notdir $*)\.o\) *:,$(dir $#)\1 $#: ,' > $#.tmp
sed. The rules in the output will have the same path as the source files, but whoever wrote this rule wants the objects files in a different directory so we need sed to substitute the paths.
Sed captures any rules with targets ending with .o that match $(notdir $*). $* is another automatic variable that expands to the pattern that matches % in the current rule, and notdir will strip any directory parts so you're left with the filename stem with no extension.
Sed then prepends $(dir $#) to the object file target, which is the same thing as $(#D) we saw above, and adds the dependency file itself ($#) as a target of the same prerequisites. This output is redirected to a file with the name of the current target + .tmp.
mv $#.tmp $#
Moves the previous file to the real target of the rule.
Side note: if you don't mind the dependency files being generated in the same directory as the object files then these recipes are outdated, you can achieve the same thing with something like:
sources := src/somefile1.c src/somefile2.c
objects := $(sources:src/%.c=obj/%.o)
deps := $(objects:.o=.d)
CFLAGS := -MMD -MP
.PHONY: all
all $(objects)
$(objects): obj/%.o: src/%.c
$(COMPILE.c) $(OUTPUT_OPTION) $<
-include $(deps)

Makefile: what does $#~ mean?

I am working on an old makefile that contains the following snippet to generate a shared library:
lib$(LIBNAME).so.$(SOLIBREV): $(OBJS)
$(RM) $#~
#SONAME=`echo $# | sed 's/\.[^\.]*$$//'`; set -x; \
$(CC) -o ./$#~ -shared -Wl,-soname,$$SONAME $(OBJS) $(SOEXTRALIBS) -lc;
$(MV) $#~ $#
$(MV) $# lib$(LIBNAME).so
Now I need to modify that. I know that $# specifies the target but what meaning does the tilde in "$#~" have?
By the way SOLIBREV stands for so-library-revision.
It doesn't mean anything special. It's just $# followed by a literal ~. A ~ suffix on filenames is often used for temporary files, so this recipe is using a temporary file named after the target but with the extra ~ suffix.

Make says "missing separator" for source .c file from CuTest

I'm trying to get started with CuTest to do unit testing in C.
When make-ing, I get the following error:
dev:~/bistro# make
cutest/CuTest.c:10: *** missing separator. Stop.
The file cutest/CuTest.c comes directly from the library. I have done no mods to it. Here are the concerned lines:
08 - #include "CuTest.h"
09 -
10 - /*-------------------------------------------------------------------------*
11 - * CuStr
12 - *-------------------------------------------------------------------------*/
13 -
14 - char* CuStrAlloc(int size)
Here's the Makefile I'm using, for complete reference:
NAME = bistro
SOURCES_DIR = src
OBJECTS_DIR = obj
SOURCES = $(shell find $(SOURCES_DIR) -type f -name *.c) cutest/CuTest.c
OBJECTS = $(patsubst $(SOURCES_DIR)/%.c, $(OBJECTS_DIR)/%.o, $(SOURCES))
DEPS = $(OBJECTS:.o=.d)
CFLAGS = -Wall -Werror -Wextra
COMPILER = gcc -I cutest -I $(SOURCES_DIR) $(CFLAGS)
BISTRO_MAIN = $(OBJECTS_DIR)/bistro/bistro_main.o
.PHONY: test all clean fclean re
all: $(NAME)
# header dependencies
-include $(DEPS)
$(NAME): $(OBJECTS)
$(COMPILER) -o $(NAME) $(OBJECTS)
test: $(filter-out $(BISTRO_MAIN), $(OBJECTS))
$(COMPILER) -c all_tests.c -o all_tests.o
$(COMPILER) -o test $(filter-out $(BISTRO_MAIN), $(OBJECTS)) all_tests.o
rm -f all_tests.o
$(OBJECTS_DIR)/%.o: $(SOURCES_DIR)/%.c
#if [ ! -d "$(#D)" ]; then mkdir -p $(#D); fi
$(COMPILER) -MMD -c $< -o $#
clean:
rm -Rf $(OBJECTS_DIR)/*
fclean: clean
rm -f $(NAME)
re: fclean all
What could be the cause of this error message?
EDIT 1: The makefile is indented with 4-space tabs only. Could the call to the "find" command be the cause of that? Also, how come the error says the missing separator is in the .c file?
EDIT 2: The accepted answer shows the error. In addition, the call did not work because it was searching for *.c, which should be "*.c".
It means you are using (four) spaces instead of tab symbol.
Make target's command must be indented with a tab.
See http://www.gnu.org/software/make/manual/make.html#Rule-Introduction:
Please note: you need to put a tab character at the beginning of every recipe line! This is an obscurity that catches the unwary. If you prefer to prefix your recipes with a character other than tab, you can set the .RECIPEPREFIX variable to an alternate character
Your error comes because you include a C source file into Makefile.
SOURCES = $(shell find $(SOURCES_DIR) -type f -name *.c) cutest/CuTest.c
OBJECTS = $(patsubst $(SOURCES_DIR)/%.c, $(OBJECTS_DIR)/%.o, $(SOURCES)) # cutest/CuTest.c stays cutest/CuTest.c
DEPS = $(OBJECTS:.o=.d)
-include $(DEPS) # oops, includes cutest/CuTest.c

Makefile adds itself as target

I have a Makefile for a C++ program that uses automatic dependency generation. The %.d recipe is taken from the GNU Make manual.
The problem is that somehow "Makefile" is being added as a target and then an implicit rule is causing it to assume it's an executable and using my src/%.cpp rule to try to compile src/Makefile.cpp. When looking at the debug info, this always happens right after the include is executed.
No need to remake target `build/Sprite.d'.
Considering target file `Makefile'.
Looking for an implicit rule for `Makefile'.
...
Trying pattern rule with stem `Makefile'.
Trying implicit prerequisite `Makefile.o'.
Looking for a rule with intermediate file `Makefile.o'.
I know include causes the given Makefiles to be rebuilt if necessary. Does it also try to rebuild the current Makefile? If so how do I stop it, and if not, then why is "Makefile" being added as a target?
Also, the include is executed, causing the .d files to be remade even if I specify a target on the command line, such as make clean. Is there any way to stop that from happening?
# $(call setsuffix,newsuffix,files)
# Replaces all the suffixes of the given list of files.
setsuffix = $(foreach file,$2,$(subst $(suffix $(file)),$1,$(file)))
# $(call twinfile,newdir,newsuffix,oldfile)
# Turns a path to one file into a path to a corresponding file in a different
# directory with a different suffix.
twinfile = $(addprefix $1,$(call setsuffix,$2,$(notdir $3)))
MAIN = main
SOURCE_DIR = src/
INCLUDE_DIR = include/
BUILD_DIR = build/
SOURCES = $(wildcard $(SOURCE_DIR)*.cpp)
OBJECTS = $(call twinfile,$(BUILD_DIR),.o,$(SOURCES))
DEPENDENCIES = $(call twinfile,$(BUILD_DIR),.d,$(SOURCES))
CXX = g++
LIBS = -lpng
CXXFLAGS = -I $(INCLUDE_DIR)
.PHONY: all
all: $(MAIN)
$(MAIN): $(OBJECTS)
$(CXX) $(LIBS) $^ -o $(MAIN)
include $(DEPENDENCIES)
%.o: $(BUILD_DIR)stamp
$(CXX) $(CXXFLAGS) -c $(call twinfile,$(SOURCE_DIR),.cpp,$#) -o $#
$(BUILD_DIR)%.d: $(SOURCE_DIR)%.cpp $(BUILD_DIR)stamp
# echo Generate dependencies for $ $#.$$$$; \
sed 's,\($*\)\.o[ :]*,$(BUILD_DIR)\1.o $# : ,g' $#; \
rm -f $#.$$$$
$(BUILD_DIR)stamp:
mkdir -p $(BUILD_DIR)
touch $#
.PHONY: clean
clean:
rm -rf $(BUILD_DIR)
.PHONY: printvars
printvars:
# echo $(SOURCES)
# echo $(OBJECTS)
# echo $(DEPENDENCIES)
Make will always try to remake the Makefile before executing the Makefile. To do so, make will look for rules which can be used to recreate the Makefile. Make will look for quite a few implicit rules and other obscure methods to (re)create the Makefile.
In your case, make somehow decided that the pattern rule %.o: $(BUILD_DIR)/stamp should be used to recreate the Makefile, which failed.
To prevent make from remaking the Makefile you can write a rule with an empty recipe:
Makefile: ;
Read the chapter Remaking Makefiles in the make manual for more explanation.
About the included Makefiles: Included Makefiles will always be included, regardless of the target. If the included makefiles are missing (or older than their prerequisites) then they will first be (re)created. That means a make clean will first generate the .d Makefiles, only to delete them again.
You can prevent the including for specific goals by wraping the include directive in a conditional:
ifneq ($(MAKECMDGOALS),clean)
include $(DEPENDENCIES)
endif
Here is your entire Makefile with some fixes. I marked the places where I changed something.
# Makefile
# $(call setsuffix,newsuffix,files)
# Replaces all the suffixes of the given list of files.
setsuffix = $(foreach file,$2,$(subst $(suffix $(file)),$1,$(file)))
# $(call twinfile,newdir,newsuffix,oldfile)
# Turns a path to one file into a path to a corresponding file in a different
# directory with a different suffix.
twinfile = $(addprefix $1/,$(call setsuffix,$2,$(notdir $3)))
MAIN = main
SOURCE_DIR = src
INCLUDE_DIR = include
BUILD_DIR = build
SOURCES = $(wildcard $(SOURCE_DIR)/*.cpp)
OBJECTS = $(call twinfile,$(BUILD_DIR),.o,$(SOURCES))
DEPENDENCIES = $(call twinfile,$(BUILD_DIR),.d,$(SOURCES))
CXX = g++
LIBS = -lpng
CXXFLAGS = -I $(INCLUDE_DIR)
.PHONY: all
all: $(MAIN)
$(MAIN): $(OBJECTS)
$(CXX) $(LIBS) $^ -o $(MAIN)
# -------> only include if goal is not clean <---------
ifneq ($(MAKECMDGOALS),clean)
include $(DEPENDENCIES)
endif
# ---------> fixed this target <--------------
$(BUILD_DIR)/%.o: $(SOURCE_DIR)/%.cpp $(BUILD_DIR)/stamp
$(CXX) $(CXXFLAGS) -c $(call twinfile,$(SOURCE_DIR),.cpp,$#) -o $#
# ---------> and this target <---------------
$(BUILD_DIR)/%.d: $(SOURCE_DIR)/%.cpp $(BUILD_DIR)/stamp
# echo Generate dependencies for $#;
#set -e; rm -f $#; \
$(CC) -M $(CPPFLAGS) $< > $#.$$$$; \
sed 's,\($*\)\.o[ :]*,$(BUILD_DIR)\1.o $# : ,g' < $#.$$$$ > $#; \
rm -f $#.$$$$
$(BUILD_DIR)/stamp:
mkdir -p $(BUILD_DIR)
touch $#
.PHONY: clean
clean:
rm -rf $(BUILD_DIR)
.PHONY: printvars
printvars:
# echo $(SOURCES)
# echo $(OBJECTS)
# echo $(DEPENDENCIES)

Resources