How do I force a target to be rebuilt if a variable is set? - makefile

Assume I have a build-target foo:
foo:foo.c
$(CC) $(CFLAGS) $(ARGS) -c foo.c -o foo
Now, ARGS is something that I pass on the command line:
$ make ARGS:=-DX=1 foo
So, I need to bypass make's cleverness, because the foo target does not only depend on which files have changed, but also on the value of ARGS.
Is there something in make to do this? My hack (see answer) doesn't seem to be the most elegant but it works. Anything better?

Here is a general solution to your specific problem.
You want to be able to depend on a variable as a prerequisite. That is, you can make it a prerequisite to any target in your makefile, and when the value of the variable changes, you rebuild those targets.
Here is a function that does that, you use this function to declare a variable to be dependable, and then you can use it as a prerequisite.
Note that if the variable is not used on the command line, it will still mean that variable still has a value, namely, the empty string.
define DEPENDABLE_VAR
.PHONY: phony
$1: phony
#if [[ `cat $1 2>&1` != '$($1)' ]]; then \
echo -n $($1) > $1 ; \
fi
endef
#declare ARGS to be dependable
$(eval $(call DEPENDABLE_VAR,ARGS))
foo:foo.c ARGS
$(CC) $(CFLAGS) $(ARGS) -c foo.c -o foo
In fact, we could omit the need for "declaration", and just write a similar function that will make all variables dependable by default. But I don't like that. I prefer that the users that modify makefiles I write, declare their intentions explicitly. It is good for them :)

My solution was to create a dummy phony target:
.PHONY:dummy
dummy:
#:
and have foo depend on dummy if ARGS is nonempty:
foo:foo.c $(patsubst %,dummy,$(ARGS))

Note on Mark's excellent answer
The bare necessities of Mark's answer are actually very simple. It really boils down to just:
.PHONY: phony
ARGS: phony
#if [[ `cat ARGS 2>&1` != '$(ARGS)' ]]; then echo -n $(ARGS) >ARGS; fi
The rest of his code is just to let you reproduce the recipe for other variable names without repeating yourself. Useful in practice, but the above version will help you see what's going on more easily.
In fact, my answer can even be made general (like Mark's) for any variable name, but in a less complicated way as follows:
.PHONY: phony
.ARG~%: phony
#if [[ `cat .ARG~$* 2>&1` != '$($*)' ]]; then echo -n $($*) >.ARG~$*; fi
Then you simply add .ARG~MYVARNAME as a dependency to any target to make that target depend on variable MYVARNAME.
Note that the dot in front of .ARG~% simply causes it to create a dependency-tracking file that is 'hidden' in linux.
So in your case, you would do:
foo: foo.c .ARG~ARGS
$(CC) $(CFLAGS) $(ARGS) -c foo.c -o foo

I don't understand how the other solutions are supposed to work. If the ARGS target is .PHONY or depends on a .PHONY, then it will always be run, right?
Here is my solution using the $(file) function in newer versions of gmake:
.PHONY: FORCE
define DEPENDABLE_VAR
$(1):
echo -n $($(1)) > $(1)
ifneq ("$(file <$(1))","$($(1))")
$(1): FORCE
endif
endef
#declare ARGS to be dependable
$(eval $(call DEPENDABLE_VAR,ARGS))
foo: foo.c ARGS
touch foo
And the result:
~/stuff/makevars> make foo ARGS=1
echo -n 1 > ARGS
touch foo
~/stuff/makevars> make foo ARGS=1
make: 'foo' is up to date.
~/stuff/makevars> make foo ARGS=2
echo -n 2 > ARGS
touch foo
~/stuff/makevars> make foo ARGS=2
make: 'foo' is up to date.

Related

Makefile: target-specific variables in target dependencies declaration

I'm trying to write a makefile that uses target-specific variables, but while the variables are set correctly in each target and prerequisite body, the prerequisites list itself isn't updated with the variable, thus causing the wrong prerequisite to be checked and called.
How can I update target-specific variables in the prerequisites too?
In the example below, both make foo and make bar should print "world", but make foo prints "hello".
X=hello
hello:
echo "hello"
world:
echo "world"
foo:X=world
foo:$(X)
bar:X=world
bar:
make $(X)
The goal I'm trying to achieve is that different targets will build similar prerequisites - the same files, in different folders - by passing the folders as a target-specific variable. The issue is that as in the example below, if one target is called first (foo, in the example), calling the second will not do anything.
DIR=fooDir
FILE=$(DIR)/filename
$(FILE):
touch $(FILE)
echo $(FILE)
foo: $(FILE)
bar:DIR=barDir
bar: $(FILE)
6.11 Target-specific Variable Values:
As with automatic variables, these values are only available within the context of a target’s recipe (and in other target-specific assignments).
But not in the prerequisite list. In other words, in foo:$(X) X is not target specific.
One way to achieve the desired results is:
same_files := filename another_filename
# ${1} is the target name.
# ${2} is the directory.
define similar_prerequisites
${1} : $(addprefix ${2}/,${same_files})
$(addprefix ${2}/,${same_files}) : | ${2}
touch $$#
${2} :
mkdir $$#
endef
all : foo bar
foo bar :
#echo "$# prerequisites are $^"
$(eval $(call similar_prerequisites,foo,fooDir))
$(eval $(call similar_prerequisites,bar,barDir))
Output:
$ make
mkdir fooDir
touch fooDir/filename
touch fooDir/another_filename
foo prerequisites are fooDir/filename fooDir/another_filename
mkdir barDir
touch barDir/filename
touch barDir/another_filename
bar prerequisites are barDir/filename barDir/another_filename
I needed something similar. Does this exemple suit your needs ?
# Basic rules can be ignored
# Use % to not take much space
.PHONY: foo bar
foo bar:
#printf "%s depends on %s\n" "$#" "$^"
fooDir/ barDir/:
#printf "%s\n" "Building $#"
#mkdir "$#"
fooDir/%: fooDir
barDir/%: barDir
%/filename: %/
#printf "%s\n" "Building $#"
#touch "$#"
%/filename2: %/
#printf "%s\n" "Building $#"
#touch "$#"
%/filename3: %/
#printf "%s\n" "Building $#"
#touch "$#"
fooFile = fooDir/filename2
barFile = barDir/filename2
DIR = $(*)Dir
FILE = $(DIR)/filename3
.SECONDEXPANSION:
# Here the fun begin
# See full details on https://stackoverflow.com/a/73679964/7227940
foo bar: %: $$(*)Dir/filename $$($$(*)File) $$(FILE)
output:
$ make foo; make bar
Building fooDir/
Building fooDir/filename
Building fooDir/filename2
Building fooDir/filename3
foo depends on fooDir/filename fooDir/filename2 fooDir/filename3
Building barDir/
Building barDir/filename
Building barDir/filename2
Building barDir/filename3
bar depends on barDir/filename barDir/filename2 barDir/filename3
The tricks is in .SECONDEXPANSION: all target defined after (that's why I put it at the end) will get a second expansion. The $(*) aka the stem of matched rules will only become available in the second expansion. This allow us to build name that is linked to the target.
The pattern targets: %: deps allow to match only on listed target and not catch all target.
I prefer the version 1 or 2 because the third version you might be tempted to use in place before .SECONDEXPANSION: and thus you will get weird error messages. If you really want to use 3 then put this special target at the top. Also 3 might have wrong value (you might want MAKECMDGOALS or something like that instead of *) if used with multiple wildcard rule. Just remember if you want to use 3 then put $$ either in DIR in FILE definition or in the use site.

Change directory before running any targets

I have the following use case where I read a variant at the top of a Makefile and then according this variable I must change directory to execute all the targets. I'd avoid to repeat in each target something like
my_target:
cd $(MY_DIR) &...
Any approach I could use to achieve that?
It's not 100% clear to me what you need but something like the following should work
ifndef submake
export submake=1
variant := somedir
$(MAKECMDGOALS):
$(MAKE) -C $(variant) -f $(realpath $(MAKEFILE_LIST)) $#
else
#actual targets defined here
foo:
#echo $#
bar:
#echo $#
endif
Go to the directory (cd) where you want Make to operate, and then
make -f /path/to/Makefile

Dynamic include directive in a Makefile

Let's consider this Makefile:
.SUFFIXES:
.DEFAULT_GOAL := all
out=foo
clean:
rm -f vars.mk
rm -f $(out)
vars.mk: vars.mk.default
#echo "Regenerating $#..."
cp $< $# # Let's assume the translation is much complex than a cp
-include vars.mk
ifeq ($(filter foo,$(FOO)),)
$(error FOO undefined)
endif
all: $(out)
$(out): vars.mk
echo "Cow says: I am not a $(FOO)." > $#
And the file vars.mk.default
FOO = foo bar
I would like to regenerate my targets if vars.mk.default is updated. Furthermore, as double check, one must check that foo exists in $(FOO).
How to force make to regenerate vars.mk if vars.mk.default is updated?
In other words, I would like this output:
$ make clean
$ sed 's/dog/cat/' vars.mk.default
$ make foo
Regenerating vars.mk...
echo "Cow says: I am not a cat" > all
$ make foo
make: Nothing to be done for 'all'.
$ sed 's/cat/dog/' vars.mk.default
$ make
Regenerating vars.mk...
echo "Cow says: I am not a dog" > all
$ rm vars.mak
$ make
Regenerating vars.mk...
echo "Cow says: I am not a dog" > all
To avoid failing if vars.mk doesn't exist, just check for it first:
ifeq ($(wildcard vars.mk),vars.mk)
ifeq ($(filter foo,$(FOO)),)
$(error FOO undefined)
endif
endif
My goal is to regenerate my targets if vars.mk.default is updated.
In this case make your targets depend on that file, but filter it out in the recipes, e.g.
foo.o : foo.cc vars.mk.default
$(COMPILE) $(filter-out vars.mk.default,$^)
In the case vars.mk does not exist, make fails on the ifeq and do not generates vars.mk.
Make is going to build vars.mk and restart, see How Makefiles Are Remade for more details.
So, to avoid that error, check first if FOO is defined with ifdef FOO.
A couple of things. First, you should put a - in front of the include to prevent a warning from popping up if the file does not exist:
-include vars.mk
This will not cause a fatal error if vars.mk is not generated, but because the vars.mk rule would fail in this case, you would get your error from there.
You can then check if $(FOO) contains foo from within a recipe:
checkForFoo: vars.mk
#[[ $(FOO) =~ .*foo.* ]] || false
all:checkForFoo
The recipe is only run after the vars.mk was generated and included, so it should only fail in the conditions you want.

How to pass down option -f SomeMakefile to a sub-make? [here with GNUMake]

When invoking itself recursively via some $(MAKE) foo in recipes, GNUMake passes down some of the options it was called with, but not all of them. In particular it does not pass down a -f SomeMakefile option. See section 5.7.3 of manual.
How can I find whether make was invoked with some -f option and how can I pass it down to a sub-make ?
To make the question concrete, here is what my SomeMakefile contains:
%.pdf : %.tex
pdflatex $(PDFLATEXFLAGS) $*
#if [ -f $*.log ] ; then $(MAKE) --silent $*.slw; fi
The problem is that how to make foo.slw is defined in SomeMakefile and the recursive make won't use it.
You can get the name of the makefile from MAKEFILE_LIST variable. E.g.:
${MAKE} -f $(lastword $(MAKEFILE_LIST))
If your makefile includes other makefiles you may like to store the name of the makefile early into an immediately assigned variable, e.g.:
# Somewhere at the top of your makefile, before any includes
this_makefile := $(lastword ${MAKEFILE_LIST})
# and use it later
some_rule :
${MAKE} -f ${this_makefile}
Alternatively, if you know that your makefile is always the first one read by make, then it is going to be in the front of MAKEFILE_LIST, e.g. $(firstword ${MAKEFILE_LIST}).

Makefile is skipping certain dependencies

So I am writing a makefile that will take some files (*.in) as input to my C++ program and compare their output (results.out) to given correct output (*.out).
Specifically I have files t01.in, t02.in, t03.in, t04.in, and t05.in.
I have verified that $TESTIN = t01.in t02.in t03.in t04.in t05.in.
The problem is that it seems to run the %.in: %.out block only for three of these files, 1,3, and 4. Why is it doing this?
OUTPUT = chart
COMPILER = g++
SOURCES = chart.cpp
HEADERS =
OBJS = $(SOURCES:.cpp=.o)
TESTIN = tests/*.in
all: $(OUTPUT)
$(OUTPUT): $(OBJS)
$(COMPILER) *.o -o $(OUTPUT)
%.o: %.cpp
clear
$(COMPILER) -c $< -o $#
test: $(TESTIN)
%.in: %.out
./$(OUTPUT) < $# > tests/results.out
printf "\n"
ifeq ($(diff $< tests/results.out), )
printf "\tTest of "$#" succeeded for stdout.\n"
else
printf "\tTest of "$#" FAILED for stdout!\n"
endif
Additionally, if there is a better way of accomplishing what I am trying to do, or any other improvements I could make to this makefile (as I am rather new at this), suggestions would be greatly appreciated.
EDIT: If I add a second dependency to the block (%.in: %.out %.err), it runs the block for all five files. Still no idea why it works this way but not the way before.
First, I don't see how TESTIN can be correct. This line:
TESTIN = tests/*.in
is not a valid wildcard statement in Make; it should give the variable TESTIN the value tests/*.in. But let's suppose it has the value t01.in t02.in t03.in t04.in t05.in or tests/t01.in tests/t02.in tests/t03.in tests/t04.in tests/t05.in, or wherever these files actually are.
Second, as #OliCharlesworth points out, this rule:
%.in: %.out
...
is a rule for building *.in files, which is not what you intend. As for why it runs some tests and not others, here is my theory:
The timestamp of t01.out is later than that of t01.in, so Make decides that it must "rebuild" t01.in; likewise t03.in and t04.in. But the timestamp of t02.out is earlier than that of t02.in, so Make does not attempt to "rebuild" t02.in; likewise t05.in. The timestamps of t02.err and t05.err are later than those of t02.in and t05.in, respectively, so when you add the %.err prerequisite, Make runs all tests. You can test this theory by checking the timestamps and experimenting with touch.
Anyway, let's rewrite it. We need a new target for a new rule:
TESTS := $(patsubst %.in,test_%,$(TESTIN)) # test_t01 test_t02 ...
.PHONY: $(TESTS) # because there will be no files called test_t01, test_t02,...
$(TESTS): test_%: %.in %.out
./$(OUTPUT) < $< > tests/results.out
Now for the conditional. Your attempted conditional is in Make syntax; Make will evaluate it before executing any rule, so tests/result.out will not yet exist, and variables like $< will not yet be defined. We must put the conditional inside the command, in shell syntax:
$(TESTS): test_%: %.in %.out
./$(OUTPUT) < $< > tests/results.out
if diff $*.out tests/results.out >/dev/null; then \
echo Test of $* succeeded for stdout.; \
else echo Test of $* FAILED for stdout!; \
fi
(Note that only the first line of the conditional must begin with a TAB.)

Resources