If condition inside a target in a Makefile - makefile

I want to do something like this, where I want to run a svn commit if a file has changed. The file has a timestamp which always changes. So if anything more than timestamp changes , then I want to commit the file.
The makefile will have something similar to this. But the If condition is not working properly. It is getting executed even when if is not satisfied. Can someone help me out what is the problem in If here.
UPDATE_STATE_FILE :
$(eval NO_LINES_CHANGES_IN_STATE = $(shell svn di STATE/build.state --diff-cmd=diff -x --normal | grep "^[<>]" | wc -l))
#echo $(NO_LINES_CHANGES_IN_STATE)
ifneq ($(strip $(NO_LINES_CHANGES_IN_STATE)), 2)
ifneq ($(strip $(NO_LINES_CHANGES_IN_STATE)), 0)
#echo $(NO_LINES_CHANGES_IN_STATE)
$(SVN) commit;
$(SVN) update;
endif
endif

You cannot mix make conditionals inside command rules. Make conditionals are like preprocessor statements in C or C++; they are handled as the file is read in, before any processing (like running rules) is performed.
If you want conditionals inside the rules you have to write the rule using shell conditionals, not make conditionals:
UPDATE_STATE_FILE :
#NO_LINES_CHANGES_IN_STATE=`svn di STATE/build.state --diff-cmd=diff -x --normal | grep "^[<>]" | wc -l`; \
echo $$NO_LINES_CHANGES_IN_STATE; \
if [ $$NO_LINES_CHANGES_IN_STATE -ne 2 ] && [ $$NO_LINES_CHANGES_IN_STATE -ne 0 ]; then \
echo $$NO_LINES_CHANGES_IN_STATE; \
$(SVN) commit; \
$(SVN) update; \
fi

After reading the answer given by #MadScientist, I came up with a different approach.
Not knowing if shell conditionals will work in any environment (windows vs. linux), I wrapped the rules inside the conditional instead of having a conditional inside the rule. i.e.
ifdef MY_FLAG
%.o: %.c
$(CC) -o $# -c $^ $(CFLAGS)
else
%.o: %.c
#$(CC) -o $# -c $^ $(CFLAGS)
endif
Good luck to anyone visiting here.
EDIT
As pointed out by James Moore in the comments, this different approach requires care and notice as to when and how variables are defined in relation to the placement of the if statement in the control flow.

Related

Makefile assertions with if-else statements (How to run only when needed?)

I have some variables defined in my Makefile which I pass to a compiler when I need to build my software. However, I need to make sure that these variables satisfy certain boolean identities at the level of the Makefile. I am currently doing something like the following.
SHELL := /bin/bash
NUMBER := 1
all: test.x
%.x: %.c ASSERT
#echo "Compiling $< -> $# with NUMBER=$(NUMBER)."
#gcc $< -D NUMBER=$(NUMBER) -o $#
ASSERT: Makefile
#if (( $(NUMBER) < 0 || $(NUMBER) > 4 )); then \
echo "ERROR: NUMBER must be positive and less than 4."; \
exit 1; \
fi
#echo "Ran ASSERT. No errors found."
The problem with the above setup is that it always runs the compilation recipe even if the target already exists and nothing has changed.
How can I run Makefile assertions properly so that targets are made only when there are changes in the Makefile variables?
You have said that all your object files depend on the prerequisite ASSERT. That prerequisite does not exist, so make will try to create it. Once make tries to create it, it will be considered "very new" and so any target that depends on it will be considered out of date and rebuilt.
Then the next time make runs, it sees that the prerequisite ASSERT does not exist, so it will try to create it and it will be considered "very new". Etc. etc.
If you don't want everything to be rebuilt, then your recipe for ASSERT must create the file ASSERT when it succeeds:
ASSERT: Makefile
#...
#touch $#
#echo "Ran ASSERT. No errors found."
Just remember that this won't have any impact if you run make NUMBER=10 rather than editing the makefile. Since the makefile isn't changed, ASSERT won't be out of date and so its recipe won't be invoked.
ETA
If all you want to do is verify that no one can use a NUMBER value that's illegal, ever, you can just test that directly with make rather than using a recipe to do it:
SHELL := /bin/bash
NUMBER := 1
$(if $(filter $(NUMBER), 0 1 2 3 4),\
$(info NUMBER is good),\
$(error ERROR: NUMBER must be positive and less than 4.))
all: test.x
%.x: %.c ASSERT
#echo "Compiling $< -> $# with NUMBER=$(NUMBER)."
#gcc $< -D NUMBER=$(NUMBER) -o $#
As your assertion is not related to *.c, can you make all depend on ASSERT:
SHELL := /bin/bash
NUMBER := 1
all: ASSERT test.x
%.x: %.c
#echo "Compiling $< -> $# with NUMBER=$(NUMBER)."
#gcc $< -D NUMBER=$(NUMBER) -o $#
ASSERT: Makefile
#if (( $(NUMBER) < 0 || $(NUMBER) > 4 )); then \
echo "ERROR: NUMBER must be positive and less than 4."; \
exit 1; \
fi
#echo "Ran ASSERT. No errors found."

makefile conditional recipe not executing

I have a makefile which produces an executable from several object files and I include a version number in each object file as it is compiled. However, I want the version number to be incremented only when a particular object file is created (ptarget below, the one containing main). I tried to do this using a conditional statement in the recipe:
ptarget:=$(addsuffix .obj,$(ouf))
%.obj : %.cpp
$(CXX) $(CXXFLAGS) $< -Fo$#
$(info $(ptarget))
$(info $#)
ifeq ($#, $(ptarget))
perl $(perlDir)versionBump/bump.pl -inc -f $(versionFile)
endif
I can see from the info commands that only when ptarget is built that $# == $(ptarget) -- I also tried using strip command to make sure no hidden whitespace, but the perl command to increment the version is never executed, it starts with a tab.
Just to add, this all works just fine without the conditional but the increment happens multiple times during a build, which is what I am trying to avoid. This example suggests it should work but...
This is a very common misunderstanding about make. ifeq is a make statement and is evaluated when make parses the Makefile. What you need is a conditional that gets evaluated when the recipe is executed by the shell, that is a shell if:
%.obj : %.cpp
$(CXX) $(CXXFLAGS) $< -Fo$#
$(info $(ptarget))
$(info $#)
if [ "$#" = "$(ptarget)" ]; then \
perl $(perlDir)versionBump/bump.pl -inc -f $(versionFile); \
fi
Do not forget the line continuations (the trailing \).

Conditional Makefile

I'm working on a Makefile with changeable sources and compiler.
Basically, what I want it to do is display a message in green if the compilation worked out well and in red otherwise. Additionally I want to avoid displaying the usual error messages (and compilation messages) a Makefile produces. (As I tried to do with all the '#')
Here's what I have for now :
COMP = gcc
NAME = test
RM = rm -f
SRC = main.c
OBJS = $(SRC:.c=.o)
CFLAGS = -Werror
all: $(NAME)
$(NAME):
#$(COMP) -c $(SRC)
#$(COMP) -o $(NAME) $(OBJS)
ifeq ($?, 0)
#echo -e '\033[1;32;40mCompilation : OK\033[0m'
else
#echo -e '\033[1;31;40mCompilation : ERROR\033[0m'
endif
clean:
#$(RM) $(OBJS)
fclean: clean
#$(RM) $(NAME)
re: fclean all
.PHONY: all clean fclean re
All it does is display "Compilation : ERROR" when it compiles well, but I thought that if $? equals 0 that meant it worked out, so I can't find any explanation.
Would you know how to make it do what I'm looking for?
Thanks a lot.
EDIT : Wonderful help from many of you, I'm still looking into the recipe but I've found a way to simply display when it succeeded and when it failed.
$(NAME): $(OBJS)
#$(COMP) $(OBJS) -o $(NAME) && echo -e "\033[32mCompilation: OK\033[0m" || echo -e "\033[31mCompilation: ERROR\033[0m"
I'll keep on digging, thanks.
$? is a shell variable not a make variable, you are testing a make variable with that if statement and the recipe for your target only ever has the one echo line in it.
(See the output from make -qp to see what I mean.)
To do what you want you would need a shell test on $? however realize that make will exit on the first failing command so you will never see the failure echo output this way (unless you capture/hide the failure from make with an if or similar construct on the compilation command).
Something like this will work for capturing/hiding the exit status from the compilation from make but allow you to use it.
#if $(COMP) -c $(SRC); then \
echo -e '\033[1;32;40mCompilation : OK\033[0m'; \
else \
_ret=$$?; \
echo -e '\033[1;31;40mCompilation : ERROR\033[0m'; \
exit $$_ret;\
fi
The bit with _ret is to have make exit with the exit code of the compilation and not a static exit 1 or whatever.
What you want is something like this:
$(NAME):
#$(COMP) -c $(SRC)
#$(COMP) -o $(NAME) $(OBJS); \
if [ $$? == 0 ]; then \
echo -e '\033[1;32;40mCompilation : OK\033[0m'; \
else \
echo -e '\033[1;31;40mCompilation : ERROR\033[0m'; \
fi; \
true
Notice that I used the '\' to concatinate the bash command with the if statement, so they all appear as a single recipe. I also added a true statement to the end such that no matter what the if statement returns, the concatenated recipe will return true, and make will not fail. You could also do:
$(NAME):
#$(COMP) -o $(NAME) $(OBJS) || echo ....
where the echo would only print if the previous command failed.

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.)

Getting Quiet Make to echo command lines on error

I have a Makefile building many C files with long long command lines and we've cleaned up the output by having rules such as:
.c${MT}.doj:
#echo "Compiling $<";\
$(COMPILER) $(COPTS) -c -o $# $<
Now this is great as the # suppresses the compilation line being emitted.
But when we get an error, all we get is the error message, no command line.
Can anyone think of a "neat" way to emit the command line?
All I can think of doing is echoing it to a file and have a higher level make catch the error and cat the file. Hacky I know.
Tested and it worked (GNU make in Linux):
.c${MT}.doj:
#echo "Compiling $<";\
$(COMPILER) $(COPTS) -c -o $# $< \
|| echo "Error in command: $(COMPILER) $(COPTS) -c -o $# $<" \
&& false
This question is pretty old, but for those of you Googling, I think what I’ll do in this situation is alias make to make -s (silent mode) in my shell, and only put the # prefix before lines where echo or other diagnostic commands are being invoked. When I want the full output from make, I will override my alias by calling it as \make.
Also note that in this situation that you’ll need to do the typical thing and put the #echo on its own line, with the actual rule commands on separate lines and without #’s.
A simple solution would be to use a simple script abc like the following:
#!/bin/bash
$#
code=$?
if (( code )); then
echo error running $#
fi
exit $code
Then you can write abc $(COMPILER) $(COPTS) -c -o $# $< in your Makefile. Do note that this does not work when you have pipes or redirects (as they will be applied to abc instead of the command you want to run).
You can also just put similar code directly in the Makefile if that's preferable.
I recently used a utility called logtext for the likes of tracking what output had occurred during the course of a bat file executing. Check it out, you may find this pretty useful if you want to know what error occurred where.

Resources