makefile recipe specific variables and $# evaluation - makefile

I'm trying to set up a makefile which consists of a debug and release target.
There are some target specific variables, mainly folder names, which are set properly in the different cases, but do not apply correctly for the $# automatic variable. Folowing code:
SRC_DIR = src
OBJ_ROOT_DIR = obj
REL_DIR = release
DBG_DIR = debug
TGT_DIR = $(REL_DIR)
SRC_LIST = main.cc prime.cc
SRC_SUFF = cc
SRCS = $(patsubst %.cc, $(SRC_DIR)/%.$(SRC_SUFF), $(SRC_LIST))
OBJ_DIR = $(OBJ_ROOT_DIR)/$(TGT_DIR)
OBJS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(SRC_LIST))
all: $(OBJS)
release: all
debug: TGT_DIR = $(DBG_DIR)
debug: OBJ_DIR = $(OBJ_ROOT_DIR)/$(TGT_DIR)
debug: OBJS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(SRC_LIST))
debug: $(OBJS)
$(OBJS):$(OBJ_DIR)/%.o: $(SRC_DIR)/%.$(SRC_SUFF)
#echo "TGT_DIR: $(TGT_DIR) OBJ_DIR: $(OBJ_DIR) OBJS: $(OBJS)"
#echo "Compiling $< to $# ..."
#echo "... done !"
#echo "."
Running make release produces the following expected output:
TGT_DIR: release OBJ_DIR: obj/release OBJS: obj/release/main.o obj/release/prime.o
Compiling src/main.cc to obj/release/main.o ...
... done !
.
TGT_DIR: release OBJ_DIR: obj/release OBJS: obj/release/main.o obj/release/prime.o
Compiling src/prime.cc to obj/release/prime.o ...
... done !
Wheras make debug leads to the following unexpected:
TGT_DIR: debug OBJ_DIR: obj/debug OBJS: obj/debug/main.o obj/debug/prime.o
Compiling src/main.cc to obj/release/main.o ...
... done !
.
TGT_DIR: debug OBJ_DIR: obj/debug OBJS: obj/debug/main.o obj/debug/prime.o
Compiling src/prime.cc to obj/release/prime.o ...
... done !
I cannot figure out, why $# doesn't evaluate to the desired debug version in the second case. The variables seem to be set correctly though. How can I achieve to have the correct directory name when executing the recipe?
Thanks in advance!

You have quite a few options / ways to do this.
One quick way is to check the contents of MAKECMDGOALS (these are the words after make e.g. release or debug). Note: there can be more then one - but if your makefile is simple and just has one the following will work:
eg:
ifneq ($(MAKECMDGOALS),debug)
TGT_DIR = $(DBG_DIR)
OBJ_DIR = $(OBJ_ROOT_DIR)/$(TGT_DIR)
OBJS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(SRC_LIST))
endif
Another option is to use second expansion, I am just going to put a link: secondary expansion
Yet another option is to setup some variables in your rule, then export them and call make again (recursively), perhaps something like this:
eg (incomplete):
# Note you can just export the ones you want but... for ease of the example:
.EXPORT_ALL_VARIABLES
debug: TGT_DIR = $(DBG_DIR)
debug: OBJ_DIR = $(OBJ_ROOT_DIR)/$(TGT_DIR)
debug: OBJS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(SRC_LIST))
debug:
$(MAKE) do_build
do_build: $(OBJS)
...etc...
Where in your first call to make it setup up some variables in the debug rule. In the second it calls do_build directly and the variables are setup already. Recursive make is generally frowned upon - but I found many times its the easiest way to get certain things done.
I'd probably err towards option 1. for its simplicity. There are even more ways to do this, but it sort of depends how you want to roll - I am just setting out some options that keep your makefile structure more or less the same...

Related

makefile target-specific changing srcs files

I'm trying to write a Makefile with a rule to make the project with another main.cpp file, because I'm testing my code with different options
I have different versions of the main function, that I put inside differents files : main.cpp, main_1.cpp, main_2.cpp, ..., to test different versions of my code, and they all have the same dependencies
first I was just commenting and un-commenting the Makefile variable MAIN that define the main.cpp file, but I was hoping there is a way to choose the one I want to try with a specific rule ?
I tried something with target-specific variables but it didn't work :
# # # # # # #
# VARIABLES #
# # # # # # #
NAME = my_program
VPATH = srcs
CXX = c++
CXXFLAGS = -I ./headers
OBJS = $(SRCS:%.cpp=%.o)
MAIN = main.cpp
#MAIN = main_1.cpp
SRCS = $(MAIN) file.cpp
# # # # #
# RULES #
# # # # #
all: $(NAME)
# target-specific variables
test-1: MAIN = main_1.cpp
test-1: re
$(NAME) : $(OBJS)
$(CXX) $(OBJS) -o $(NAME)
clean:
rm -f $(OBJS)
fclean: clean
rm -f $(NAME)
re: fclean all
.PHONY : all clean fclean re
the error output for main test_1 is :
c++ -I ./headers -c -o main.o srcs/main.cpp
c++ -I ./headers -c -o file.o srcs/file.cpp
c++ main_1.o Webserv.o -o my_program
c++: error: main_1.o: No such file or directory
Makefile:21: recipe for target 'my_program' failed
make: *** [my_program] Error 1
I think, then, that target-specific is not the right tool for what I'm trying to do.
Does Make provide a way to accomplish that (modifying the list of srcs files when calling a specific rule, and having the compilation working great with the new srcs files) ?
I'm vaguely thinking something like this.
test-%: main_%.cpp file.cpp
Now, make test-1 will produce an executable with that name from main_1.cpp instead of main.cpp, and similarly test-2 from main_2.cpp, etc.
If you have subsequent targets which hardcode my_program which should actually depend on which version you made, this might not be suitable, or at a minimum, you'd have to refactor those to use the current output executable. Similarly, you might want to add test-[1-9] to the files to remove in the clean target (or perhaps add a realclean target to remove them too).
Tangentially, several of your make variables don't seem to serve any immediate purpose. Putting stuff in variables makes sense for things you want to be able to override at compile time, or vaguely for making a general-purpose Makefile which can be applied with only minor modifications across several projects; but in isolation, these seem like unnecessary complexities you should probably avoid for the time being.
Your immediate problem could perhaps be solved by refactoring the dependency chain, but on the whole, I'd recommend keeping it as simple as possible. make already knows how to compile common source formats; all you really need to put in the Makefile are the dependencies which are not trivially obvious and any .PHONY targets, and overrides to select e.g. a specific default action.

How can I use Target-specific Variable Values and call other rules whilst using parallel execution?

I have a makefile with the following lines:
debug: CFLAGS += $(DEBUGFLAGS)
debug: clean all
What I want to do is run 'clean' and 'all' with the Target Specific Variable values. This works fine and as expected.
But if I ran this with parallel execution 'clean' might destroy the files being create by 'all'.
So if I do something like the following:
debug: CFLAGS += $(DEBUGFLAGS)
debug:
$(MAKE) clean
$(MAKE) all
This will ensure that the order of the rules is respected. But the Target Specific Variables will not be taken into the new invocations of make.
So I was wondering how I can use both Target Specific Variables and parallel execution.
Why not just pass through the values as well?
debug: CFLAGS += $(DEBUGFLAGS)
debug:
$(MAKE) clean CFLAGS='$(CFLAGS)'
$(MAKE) all CFLAGS='$(CFLAGS)'
Not sure which version of make introduced it (3.8? 4.0?), but if you add 'clean' to the pseudo-target .NOTPARALLEL, then clean is not run in parallel, and other targets are. I use make -j clean all and this seems to work as expected.
e.g.
...<variables defined here>...
all: $(TARGETS)
clean:; rm -rf $(TARGETS) ...
...<rules defined here>...
.PHONY: all clean
.NOTPARALLEL: clean

Changing build flags and recompiling

I have a make file with a number of phony targets, they all compile the same code just with different compilation flags.
EXECUTABLE=ecis
#debug build
.PHONY: debug
debug: FLAGS=-g
debug: $(EXECUTABLE)
#No optimization
.PHONY: opt0
opt0: FLAGS=
opt0: $(EXECUTABLE)
#level 1 optimization
.PHONY: opt1
opt1: FLAGS=-O1
opt1: $(EXECUTABLE)
#level 2 optimization
.PHONY: opt2
opt2: FLAGS=-O2
opt2: $(EXECUTABLE)
...
$(EXECUTABLE):$(FORTRAN_OBJECTS) $(CPP_OBJECTS)
$(CPP) $(FLAGS) $(FORTRAN_OBJECTS) $(CPP_OBJECTS) -lgfortran -o $#
...
When I first run the makefile with one build make opt2 option it runs just fine.
If I subsequently want to run make with another build option say make debug it claims that the target is up to date. I understand why it is doing this, make doesn't realize that the flags changed and so as far as make is concerned nothing has changed if the files haven't changed.
That said, is there an easy way around it other than calling make cleanall (which deletes the .o files and the executable)? Is there some way for make to recognize the different flags as changing the compilation? Are there any other paths to have it "do the right thing"?
Here's an example that should work:
.compile_flags: Makefile
[ "`cat $#`" = '$(FLAGS)' ] || echo '$(FLAGS)' > $#
$(FORTRAN_OBJECTS) $(CPP_OBJECTS) $(EXECUTABLE): .compile_flags
An alternative is to generate the object files and executable into a separate subdirectory for each different base target. Then they won't overlap. This has the added benefit that you don't have to recompile the world for each type of build (only since the last time you did that build). But it uses more disk space and may cause other issues if you have other parts of the system expecting things to live where they do now.
To put things in other directories can't be done with target-specific variables. The simplest way to do it is to use one instance of recursive make instead, like this:
EXECUTABLE=ecis
#debug build
.PHONY: debug
debug: FLAGS=-g
#No optimization
.PHONY: opt0
opt0: FLAGS=
#level 1 optimization
.PHONY: opt1
opt1: FLAGS=-O1
#level 2 optimization
.PHONY: opt2
opt2: FLAGS=-O2
debug opt0 opt1 opt2:
$(MAKE) OUTDIR=obj_$# FLAGS=$(FLAGS) obj_$#/$(EXECUTABLE)
...
FORTRAN_OBJECTS := $(addprefix $(OUTDIR)/,$(FORTRAN_OBJECTS))
CPP_OBJECTS := $(addprefix $(OUTDIR)/,$(CPP_OBJECTS))
$(OUTDIR)/$(EXECUTABLE):$(FORTRAN_OBJECTS) $(CPP_OBJECTS)
$(CPP) $(FLAGS) $(FORTRAN_OBJECTS) $(CPP_OBJECTS) -lgfortran -o $#
...
And, you'll have to create pattern rules for your object files like:
$(OUTDIR)/%.o : %.cpp
...
and ditto for FORTRAN.
If you're limited to GNU make itself, you'll have trouble achieving your goal. As #MadScientist suggested, you can basically get what you want with some shenanigans. John Graham-Cumming wrote up a good explanation in his old "Ask Mr. Make" column: Rebuilding when CPPFLAGS Changes.
If you can use other make implementations, you might check out Electric Make, a reimplementation of GNU make designed for performance and reliability. It includes a feature called "ledger" which provides precisely this functionality.
Disclaimer: I'm the architect and lead developer of Electric Make

Generate a list of files/objects dynamically from within makefile

I am trying to do this:
From a directory, pick all the C (.c) files, generate .o and add it to
my final target executable. The C files can be added or removed at anytime, so when I
run make for my target, the available C files from the directory has to be picked
to compile and link with my target.
So far, I have the following:
define test_tgt =
DIR = full/path/to/dir
FILES = $(wildcard $(DIR)/*.c)
OBJS = <rule-to-convert-C-to-O>
endef
get_new_files:
$(eval $(test_tgt))
final-target: get_new_files
$(CC) <other-objs> $(OBJS)
Somehow this doesn't seem to work. I see a lot of similar examples, but not sure what
is wrong here. If this approach is not correct, can anyone suggest a better way to
accomplish this.
TIA.
You are trying to program a check that make does by itself.
Just list $(OBJS) as dependencies of final-target.
Something like this should work under GNU make:
DIR = full/path/to/dir
FILES = $(wildcard $(DIR)/*.c)
OBJS = $(subst .c,.o,$(FILES))
final-target: $(OBJS)
$(LD) -o $# $+ # or similar
Full documentation is here: https://www.gnu.org/software/make/manual/html_node/Text-Functions.html

Makefile variable expansion

The following is a contrived example Makefile illustrating a problem that I'm having.
release: TYPE := release
FILE = main.cpp
OBJDIR = dist/$(TYPE)
OBJS = $(patsubst %.cpp,$(OBJDIR)/%.o,$(FILE))
release: $(OBJS)
#echo "just created: " $(OBJS)
%.o:
#echo "create $#"
When I run 'make release' the output is:
create dist//main.o
just created: dist/release/main.o
How can I ensure that the $(OBJS) dependency of release target is expanded to dist/release/main.o and not dist//main.o . Also what is the reason for it expanding to dist//main.o?
The reason for it expanding to dist//main.o is that TYPE is a target-specific variable. The value of this kind of variable is only available within the context of a target's recipe (and in other target-specific assignments.
This means the value of TYPE is empty in the prerequisites for that rule.

Resources