Makefile improvement when intermediate files do not really change - makefile

I have a Makefile like this
all: *.foo
./finalstep *.foo > $#
%.foo: %.bar
./secondstep < $< > $#
%.bar: %.baz
./firststep < $< > $#
The thing is that often changes in a .baz file are minor in the sense that the .bar file produced after the change is the same (content-wise, or as would be detected by diff) as before the change.
Since secondstep and finalstep (and possible some more intermediate steps) are expensive it would be preferable if the lack of change in the .bar files could be detected and thus the invocation of secondstep (and maybe even finalstep) spared. Is there any way to achieve this?
My attempt to do something like this is as follows:
%.bar: %.baz
touch $#; cp $# $#.backup; ./firststep < $< > $#
%.foo: %.bar
diff -q $< $<.backup || ./secondstep < $< > $#
But this has a lot of drawbacks (and does not work correctly if one invokes make with arguments).
Is there any better method? Basically, make should consider two different filetimes for .bar files: One that gets updated each time firststep is run and that is used to determine whether .bar itself needs to be remade. Another that is only updated when a run of firststep results in a net change of content of the file and that is used to determine whethr .foo needs to be remade ...

I think you should experiment with something like this (not tested):
all: %.foo
./finalstep %.foo > $#
%.foo: %.bar
#if [[ -f $< && -f $<.backup ]]; then \
if diff -q $< $<.backup; then \
echo "IDENTICAL => do nothing"; \
else \
echo "DIFFERENT => proceed"; \
./secondstep < $< > $#; \
fi; \
else \
echo "NOT FOUND => proceed"; \
./secondstep < $< > $#; \
fi; \
cp $< $<.backup
%.bar: %.baz
./firststep < $< > $#;
Basically, the intention of the big recipe is to handle all the relevant combinations of $< and $<.backup (in the same subshell!):
both files present and equal (diff returns 0) => do nothing
both files present and differ (diff returns 1) => execute
one of the files not present => execute
Whatever is the above choice, make a backup at the end.

Related

Key value pair in MAKEFILE

I am new in makefle, I had need array in makefile then I found that I can achieve that having variable that their items separated with spaces, and then iterating over it.
Now I want something like map(key,value) pair to store values with keys.
Question: can I have that in makefile ?
Thanks in advance..
You can use token pasting for that:
VAR_FOO_KEY := FOO_VAL
VAR_BAR_KEY := BAR_VAL
#example lookup:
KEY := FOO_KEY
LOOKUP_VAL := $(VAR_$(KEY))
Make a file named KV.mk with the following function definitions:
# KV(3 args): in a dictionary directory record a key value pair in a file
# KV(2 args): in a dictionary directory recover value associated with a key
# KV(1 arg ): remove a dictionary directory
# KV(0 arg ): remove all dictionary directories
define KV
$(if $4,$(error K0123.mk: args>3 forbidden), \
$(if $3,mkdir -p $1.key; echo "$3" > $1.key/$2, \
$(if $2,`cat $1.key/$2`, \
$(if $1,rm -fr $1.key/*, \
rm -fr *.key \
))))
endef
Then make a client Makefile with the following contents:
include KV.mk
all: hello.cpp hello
.PHONY: hello.cpp
hello.cpp:
#echo '#include <iostream>' > $#
#echo 'int main(int argc, char** argv)' >> $#
#echo '{ std::cout << "hello" << std::endl; return 0; }' >> $#
hello: hello.cpp
#$(call KV,$#)
#$(call KV,$#,compiler,g++)
#$(call KV,$#,compiler) -o hello hello.cpp
#./$#
clean:
#rm -f hello.cpp hello
#$(call KV)
Copy these two files into an empty directory and run "make".
This method offers much liberty in having multiple dictionaries
with multiple key/value pairs per dictionary.
One dictionary may be used for the entire Makefile.
Another dictionary may be used for all rules having common needs.
Individual dictionaries may be used for each rule.
Simplifying one more step, here is a shell script encapsulating the KV idea.
#!/bin/bash
usage() {
cat <<USAGE
KV(1) -- syre(tm) user command
NAME
KV - key value store management for Makefile
SYNOPSIS
(1) KV [--help]
(2) KV [--clear]
(3) KV [OPTION]... {key}
(4) KV [OPTION]... {key}={value}
1: display usage (this text)
2: clear all dictionaries (make clean)
3: print value associated with key
4: store key value pair in the dictionary and key file
USAGE
if [ "" != "$1" ] ; then echo "KV error: $1"; fi
exit 0
}
if [ $1 = "--help" ] ; then usage;
elif [ $1 = "--clear" ] ; then rm -fr *.key;
else
case $# in
3) mkdir -p $1.key; echo "$3" > $1.key/$2;;
2) cat $1.key/$2;;
1) rm -fr $1.key;;
*) usage "KV error: 1, 2, or 3 args required";;
esac
fi
and here is the same Makefile showing that simplification.
all: hello.cpp hello
.PHONY: hello.cpp
hello.cpp:
#echo '#include <iostream>' > $#
#echo 'int main(int argc, char** argv)' >> $#
#echo '{ std::cout << "hello" << std::endl; return 0; }' >> $#
hello: hello.cpp
#./KV $#
#./KV $# compiler g++
#`./KV $# compiler` -o hello hello.cpp
#./$#
clean:
#rm -f hello.cpp hello
#./KV --clear

make fclean && make all works, make re doesn't

I'm working on an advanced makefile that I found in a book. I've got some simple rules inside:
clean to delete binaries
fclean to delete some extra files too (links to binaries generated by ln)
all to make all
re to make fclean then make all
when I do make fclean then make all, it works perfectly. When i do make re, an error occurs:
error: unable to open output file '/Users/malberte/work/libft/bin/libft/common/ft_atoi.o':
'No such file or directory'
1 error generated.
So here is my code:
$(_MODULE_NAME)_OBJS := $(addsuffix $(_OBJEXT),$(addprefix $($(_MODULE_NAME)_OUTPUT)/,$(basename $(SRCS)))) $(DEPS)
$(_MODULE_NAME)_BINARY := $($(_MODULE_NAME)_OUTPUT)/$(BINARY)$(BINARY_EXT)
$(_MODULE_NAME)_EXPOSE_BINARY := $(_ROOT)/$(BINARY)$(BINARY_EXT)
ifneq ($(_NO_RULES),T)
ifneq ($($(_MODULE_NAME)_DEFINED), T)
_CLEAN := clean-$(_MODULE_NAME)
_FCLEAN := fclean-$(_MODULE_NAME)
_ALL := all-$(_MODULE_NAME)
_RE := re-$(_MODULE_NAME)
_IGNORE := $(shell mkdir -p $($(_MODULE_NAME)_OUTPUT))
.PHONY: all re $(_ALL) $(_RE)
re: fclean all
# re: $(_RE)
# $(_RE): $(_FCLEAN) $(_ALL)
all: $(_ALL)
$(_ALL): $($(_MODULE_NAME)_BINARY)
.PHONY: $(_MODULE_NAME)
$(_MODULE_NAME): $($(_MODULE_NAME)_BINARY)
.PHONY: fclean clean $(_CLEAN)
fclean: $(_FCLEAN)
$(_FCLEAN): $(_CLEAN)
rm -rf $($(patsubst fclean-%,%,$#)_EXPOSE_BINARY)
clean: $(_CLEAN)
$(_CLEAN):
rm -rf $($(patsubst clean-%,%,$#)_OUTPUT)
$($(_MODULE_NAME)_OUTPUT)/%.o: $(_MODULE_PATH)/%.c
#$(COMPILE.c) -o '$#' '$<'
$($(_MODULE_NAME)_OUTPUT)/$(BINARY)$(_LIBEXT): $($(_MODULE_NAME)_OBJS)
#if [ "$(LIBMERGE)" = "F" ]; \
then \
$(AR) r '$#' $^; \
ranlib '$#'; \
else \
libtool -static -o '$#' $^; \
fi
$($(_MODULE_NAME)_OUTPUT)/$(BINARY)$(_EXEEXT): $($(_MODULE_NAME)_OBJS)
$(LINK.c) $^ -o '$#'
$(_MODULE_NAME)_DEFINED := T
endif
endif
I've tried lot of things, I really don't understand what is happening when I use make re and it throws the error above.
Someone has an idea please ?
You have this line in your makefile:
_IGNORE := $(shell mkdir -p $($(_MODULE_NAME)_OUTPUT))
which creates the output directory, as the makefile is being parsed. Then you run your clean target which invokes this recipe:
rm -rf $($(patsubst clean-%,%,$#)_OUTPUT)
which causes the output directory to be deleted. Then you run your all target which invokes the compiler and asks it to write the output file to $($(_MODULE_NAME)_OUTPUT)/%.o but that directory no longer exists.
So the compiler gives you the error:
error: unable to open output file '...': No such file or directory
If you run make twice, then the first time you clean and delete the directory, then when you run make all it will run the _IGNORE shell command and create the directory again so it will exist.
If you run make re one time, then the makefile is only parsed one time and the output directory is only created one time (before it's deleted).
Okay thank you so much. It drove me to think about how a makefile really works, so here is my basic solution, thanks to you:
$(_MODULE_NAME)_OBJS := $(addsuffix $(_OBJEXT),$(addprefix $($(_MODULE_NAME)_OUTPUT)/,$(basename $(SRCS)))) $(DEPS)
$(_MODULE_NAME)_BINARY := $($(_MODULE_NAME)_OUTPUT)/$(BINARY)$(BINARY_EXT)
$(_MODULE_NAME)_EXPOSE_BINARY := $(_ROOT)/$(BINARY)$(BINARY_EXT)
ifneq ($(_NO_RULES),T)
ifneq ($($(_MODULE_NAME)_DEFINED), T)
_OUTPUT_TREE := output-tree-$(_MODULE_NAME)
_CLEAN := clean-$(_MODULE_NAME)
_FCLEAN := fclean-$(_MODULE_NAME)
_ALL := all-$(_MODULE_NAME)
_RE := re-$(_MODULE_NAME)
# _IGNORE := $(shell mkdir -p $($(_MODULE_NAME)_OUTPUT))
.PHONY: all re $(_ALL) $(_RE)
re: fclean all
# re: $(_RE)
# $(_RE): $(_FCLEAN) $(_ALL)
all: $(_ALL)
$(_ALL): $($(_MODULE_NAME)_BINARY)
.PHONY: $(_MODULE_NAME)
$(_MODULE_NAME): $($(_MODULE_NAME)_BINARY)
.PHONY: fclean clean $(_CLEAN)
fclean: $(_FCLEAN)
$(_FCLEAN): $(_CLEAN)
rm -rf $($(patsubst fclean-%,%,$#)_EXPOSE_BINARY)
clean: $(_CLEAN)
$(_CLEAN):
rm -rf $($(patsubst clean-%,%,$#)_OUTPUT)
$($(_MODULE_NAME)_OUTPUT)/%.o: $(_MODULE_PATH)/%.c | $(_OUTPUT_TREE)
#$(COMPILE.c) -o '$#' '$<'
$($(_MODULE_NAME)_OUTPUT)/$(BINARY)$(_LIBEXT): $($(_MODULE_NAME)_OBJS)
#if [ "$(LIBMERGE)" = "F" ]; \
then \
$(AR) r '$#' $^; \
ranlib '$#'; \
else \
libtool -static -o '$#' $^; \
fi
$($(_MODULE_NAME)_OUTPUT)/$(BINARY)$(_EXEEXT): $($(_MODULE_NAME)_OBJS)
$(LINK.c) $^ -o '$#'
.PHONY: output-tree $(_OUTPUT_TREE)
output-tree: $(_OUTPUT_TREE)
$(_OUTPUT_TREE):
mkdir -p $($(_MODULE_NAME)_OUTPUT)
$(_MODULE_NAME)_DEFINED := T
endif
endif
I added a prerequisite order to the atomic target
$($(_MODULE_NAME)_OUTPUT)/%.o: $(_MODULE_PATH)/%.c | $(_OUTPUT_TREE)
Here is the rule:
.PHONY: output-tree $(_OUTPUT_TREE)
output-tree: $(_OUTPUT_TREE)
$(_OUTPUT_TREE):
mkdir -p $($(_MODULE_NAME)_OUTPUT)
I'll see if I need more adjustments but it seems to be the right way !

How to use eval Makefile variable from recipe to conditionally execute some commands in recipe

Goal is to apply patch ONLY if patch is not present. If patch is present don't do anything.
I used below makefile rule.
C_FILE_PATCH_SIG=###MAGIC_CODE;
C_FILE_CODE=~/code/file.c
C_PATCH_FILE=~/test.patch
.tmp/patch_c:
cp ${C_PATCH_FILE} ${SDK}
ifneq ($(PATCH_DONE), 1)
$(MAKE) applypatch || $(MAKE) helppatch
endif
#echo DONE > .tmp/patch_c
applypatch:
#echo "Patching ${C_FILE_CODE}"
if grep -Fq '${C_FILE_PATCH_SIG}' ${C_FILE_CODE} ; \
then \
echo 1 > .tmp/PATCH_PRESENT_file; \
else \
echo 0 > .tmp/PATCH_PRESENT_file;\
fi
cat .tmp/PATCH_PRESENT_file
# $(eval PATCH_PRESENT := `cat .tmp/PATCH_PRESENT_file`)
$(eval PATCH_PRESENT := $(shell cat .tmp/PATCH_PRESENT_file))
#echo "WWWWWW PATCH_PRESENT=[$(PATCH_PRESENT)] WWWWWWW"
ifeq ($(PATCH_PRESENT), 0)
#echo "Applying the patch $(PATCH_PRESENT)"
cd ~/code && git apply -v ${C_PATCH_FILE}
else
#echo "NOT Applying the patch $(PATCH_PRESENT)"
endif
helppatch:
#echo -e "\n\n\n"
#echo -e "++++++++++ Apply below patch manually then run 'make build PATCH_DONE=1' ++++++++++\n\n"
#echo -e "+++++++++++++++++++++++++++++++++++++"
#cat ${C_PATCH_FILE}
#echo -e "+++++++++++++++++++++++++++++++++++++"
#echo -e "\n\n\n"
false
But it always evaluates to the else part of ifeq.
Where am I doing wrong?
If I use the patch command of git withing the shell multiline I loose the error code returned by the git patch.
Thanks in advance...
Your ifeq will be evaluated when the makefile is first read (as opposed to when the recipe is run). The eval, on the other hand, will not be executed until the recipe is run (afterwards). Thus, PATCH_PRESENT is not equal to 0 at parse time, and make will expand the else portion of the clause. By the time the eval is run, the if statement is already evaluated and gone from memory.
BTW, a cleaner way to do this is to do everything in bash:
applypatch:
#echo "Patching ${C_FILE_CODE}"
#if grep -Fq '${C_FILE_PATCH_SIG}' ${C_FILE_CODE}; then \
echo "NOT Applying the patch"; \
else \
echo "Applying the patch"; \
cd ~/code && git apply -v ${C_PATCH_FILE}; \
fi

Use help2man in parallel automake

I'm working on a project built using GNU autotools (autoconf, automake). It does work well, but I've a problem with help2man: When building parallel (MAKEFLAGS=-j3), the project is built twice - once using "normal" target, once using the foo.1 make call.
The following is the relevant part from Makefile.am:
foo.1 : $(top_srcdir)/man/foo.x $(top_srcdir)/src/main.c $(top_srcdir)/configure.ac
$(MAKE) $(AM_MAKEFLAGS) foo$(EXEEXT)
-$(HELP2MAN) -o $# --include $< $(top_builddir)/foo
So, my question is how to write Makefile.am to support the following:
Distribute foo.1 to support systems without help2man
Do not throw errors
Rebuild manpage if necessary
I'm looking forward to your answer
foo.1 needs correct prerequisites. AIUI, help2man just needs the executable binary to be built:
foo.1 : $(top_srcdir)/man/foo.x $(top_srcdir)/configure.ac $(top_builddir)/foo
-$(HELP2MAN) -o $# --include $< $(top_builddir)/foo
so that's 3)
don't understand what you want out of 2), which is not possible in general.
dist_man_MANS = foo.1
which is 1)
There seems to be no easy solution to this question; the following works for me.
In configure.ac, you have to check for help2man. If cross-compiling, you must not run help2man, as the executable will be run. The following snippet is therefore contained:
# Man pages
AS_IF([test "$cross_compiling" = "no"], [
AM_MISSING_PROG([HELP2MAN], [help2man])
], [
HELP2MAN=:
])
For building, there is a two-level concept. First, you check if the manpage is newer than the executable; if so, to prohibit unnecessary manpage rebuilds, you check against the source using a temporary file being last altered when the manpage itself was. So, Makefile.am contains:
dist_man_MANS = foo.1
EXTRA_DIST += $(dist_man_MANS:.1=.x) common.x
MOSTLYCLEANFILES += $(dist_man_MANS:=-t)
MAINTAINERCLEANFILES += $(dist_man_MANS)
common_dep = $(top_srcdir)/common.x $(top_srcdir)/configure.ac $(top_srcdir)/.version
common_indirect_dep = $(top_srcdir)/common.x $(top_srcdir)/configure $(top_srcdir)/.version
foo.1 : $(common_indirect_dep) $(top_builddir)/foo$(EXEEXT)
foo.1-t : $(common_dep) $(top_srcdir)/main-helpversion.c
SUFFIXES += .x .1 .1-t
.x.1:
test -f $# || if test -f $(top_srcdir)/`echo $# | $(SED) -e 's,.*/,,'`; then \
touch -r $(top_srcdir)/`echo $# | $(SED) -e 's,.*/,,'` $#; \
else \
touch -d #0 $#; \
fi
touch -r $# $#-t
$(MAKE) $(AM_MAKEFLAGS) $#-t
if test -s $#-t; then \
mv -f $#-t $#; \
else \
rm -f $#-t; \
if test -s $#; then touch $#; else rm -f $#; fi; \
fi
.x.1-t:
$(HELP2MAN) -o $# -I $< -I $(top_srcdir)/common.x -s 1 foo$(EXEEXT)

Compile several projects (with makefile), but stop on first broken build?

I want to do something like:
for i in *
do
if test -d $i
then
cd $i; make clean; make; cd -;
fi;
done
And this works fine, but I want "break" the for-loop in case of a broken build.
Is there a way to do this? Maybe some kind of if-statement, that can check for success of make?
You can use Make itself to achieve what you're looking for:
SUBDIRS := $(wildcard */.)
.PHONY : all $(SUBDIRS)
all : $(SUBDIRS)
$(SUBDIRS) :
$(MAKE) -C $# clean all
Make will break execution in case when any of your target fails.
UPD.
To support arbitrary targets:
SUBDIRS := $(wildcard */.) # e.g. "foo/. bar/."
TARGETS := all clean # whatever else, but must not contain '/'
# foo/.all bar/.all foo/.clean bar/.clean
SUBDIRS_TARGETS := \
$(foreach t,$(TARGETS),$(addsuffix $t,$(SUBDIRS)))
.PHONY : $(TARGETS) $(SUBDIRS_TARGETS)
# static pattern rule, expands into:
# all clean : % : foo/.% bar/.%
$(TARGETS) : % : $(addsuffix %,$(SUBDIRS))
#echo 'Done "$*" target'
# here, for foo/.all:
# $(#D) is foo
# $(#F) is .all, with leading period
# $(#F:.%=%) is just all
$(SUBDIRS_TARGETS) :
$(MAKE) -C $(#D) $(#F:.%=%)
You can check whether the make has exited successfully by examining its exit code via the $? variable, and then have a break statement:
...
make
if [ $? -ne 0 ]; then
break
fi

Resources