Not able to run if condition inside Makefile - shell

I have the following content in my makefile:
ARCH := $(if $(GOARCH),$(GOARCH),$(shell go env GOARCH))
all: build
build:
echo "Doing the build"
if [ "$$ARCH" = "amd64" ]; then \
touch test; \
echo "inside the condition loop"; \
fi
Now, while running make command, I get the following output:
# make
echo "Doing the build"
Doing the build
if [ "$ARCH" = "amd64" ]; then \
touch test; \
echo "inside the condition loop"; \
fi
This executes, but it doesn't create the test file in the current directory.
# ls
Makefile
Any idea what am I doing wrong here? Or any tips to debug further? TIA.

By default make variables are not exported. You probably want to add 'export ARCH' after the assignment.
ARCH := $(if $(GOARCH),$(GOARCH),$(shell go env GOARCH))
export ARCH
# OR
export ARCH := $(if $(GOARCH),$(GOARCH),$(shell go env GOARCH))
For the build action to work you need ARCH to be export for two reason
The '[ "$ARCH" = "amd64" ]; refer to environment variable ARCH
The actual build will probably need ARCH set correctly to work.

Related

Makefile target unexpectadly raising error

My Makefile includes follow target
run: $(BIN_FILE)
if [ -d $(BIN_FILE) ]; then $(error Sorry BIN_FILE is directory); else ./$(BIN_FILE) $(RUN_ARGS); fi
But it raises the error no matter whether test passes or not. What is wrong? Why it raises an error even BIN_FILE is not directory? Have directive $(error... any special meaning?
Many thanks.
You can't embed a Make expression like that inside your shell script. Make performs all expansions of $(...) expressions before the shell even starts, so it sees your $(error ...) command and exits. You would need to emit this error using shell logic instead, doing something like:
run: $(BIN_FILE)
if [ -d $(BIN_FILE) ]; then \
echo "Sorry BIN_FILE is directory"; \
exit 1; \
fi
$(BIN_FILE) $(RUN_ARGS)
Or with slightly more compact logic:
run: $(BIN_FILE)
[ -d "$(BIN_FILE)" ] && { echo "$(BIN_FILE) is a directory"; exit 1; } ||:
$(BIN_FILE) $(RUN_ARGS)

How can I have a Makefile target update dependent on the value of an environment variable?

I have a make target that will have different output depending on the value of an environment variable.
How can I:
skip the dependency and not re-make the target if the environment variable has not changed the last run
make or re-make the target if the environment variable is not set or has changed
I thought I could create or conditionally update a file with the current environment variable value and then use that file as a make dependency. I couldn't find an elegant way to do that with native tools. (sed -i always updated the file's timestamp, maybe awk is possible)
How about using a shell script to update a file that holds the variable value?
SHELL = /bin/bash
var_file := var.txt
var_name := NAME
is_var_updated = [[ ! -e $(var_file) ]] || [[ "$$(< $(var_file))" != "$($(var_name))" ]]
update_var_file = echo "$($(var_name))" > $(var_file)
$(shell $(is_var_updated) && $(update_var_file))
output.txt: $(var_file)
echo "Name is $$NAME" > $#
This works like this.
$ ls
Makefile
$ NAME=foo make
echo "Name is $NAME" > output.txt
$ NAME=foo make
make: `output.txt' is up to date.
$ NAME=bar make
echo "Name is $NAME" > output.txt
Make conditionals could be a starting point:
.PHONY: all
FILE := foobar
ifdef ENV_VAR
OLD_ENV_VAR := $(shell [ -f $(FILE) ] && cat $(FILE))
ifeq ($(ENV_VAR),$(OLD_ENV_VAR))
DONTRUN := 1
endif
endif
ifdef DONTRUN
all:
#echo 'ENV_VAR unmodified'
else
$(shell printenv ENV_VAR > $(FILE))
all:
#echo 'ENV_VAR undefined or modified'
endif

Makefile test if variable is not empty

In a makefile I'm trying to
run a shell command and capture the output in a make variable
do something if the variable is not empty
I've created this simplified makefile to demonstrate my problem. Neither make a or make b executes the body of the if, I don't understand why not.
.PHONY: a b
a:
$(eval MY_VAR = $(shell echo whatever))
#echo MY_VAR is $(MY_VAR)
$(info $(MY_VAR))
ifneq ($(strip $(MY_VAR)),)
#echo "should be executed"
endif
#echo done
b:
$(eval MY_VAR = $(shell echo ''))
#echo MY_VAR is $(MY_VAR)
$(info $(MY_VAR))
ifneq ($(strip $(MY_VAR)),)
#echo "should not be executed"
endif
#echo done
I'm using
$ make --version
GNU Make 3.81
Edit: as pointed out, the vars don't need to be make vars
If you want to dynamically test the content of MY_VAR, you may have to :
a:
$(eval MY_VAR = $(shell echo ''))
$(if $(strip $(MY_VAR)),echo ok,echo no)
if evaluation will become echo ok if MY_VAR is not empty, otherwise it will become echo no
Note that, due to the time of evaluation, make conditionals (ifeq, ifneq...) cannot be used in recipes the way you tried. Use shell conditionals, instead, as shown below.
As your MY_VAR variable is used only in recipes, is target-dependent and you want it to be computed only when needed, why don't you use shell variables, instead of make variables?
$ cat Makefile
.PHONY: a b
a:
MY_VAR=$$(echo 'whatever') && \
echo '$#: MY_VAR is $$MY_VAR' && \
if [ -n "$$MY_VAR" ]; then \
echo '$#: should be executed'; \
fi && \
echo '$#: done'
b:
MY_VAR=$$(echo '') && \
echo '$#: MY_VAR is $$MY_VAR' && \
if [ -n "$$MY_VAR" ]; then \
echo '$#: should not be executed'; \
fi && \
echo '$#: done'
$ make a
a: MY_VAR is whatever
a: should be executed
a: done
$ make b
b: MY_VAR is
b: done
In case you absolutely need MY_VAR to be a target-specific make variable, but want to execute only once (per target) the shell command that produces its value, MadScientist has a wonderful trick that you should probably look at. Applied to your case, it should look like:
$ make --version
GNU Make 4.1
...
$ cat Makefile
a: MY_VAR = $(eval a: MY_VAR := $$(shell echo 'whatever'))$(MY_VAR)
b: MY_VAR = $(eval b: MY_VAR := $$(shell echo ''))$(MY_VAR)
a:
#echo '$#: MY_VAR is $(MY_VAR)' && \
if [ -n "$(MY_VAR)" ]; then \
echo '$#: should be executed'; \
fi && \
echo '$#: done'
b:
#echo '$#: MY_VAR is $(MY_VAR)' && \
if [ -n "$(MY_VAR)" ]; then \
echo '$#: should not be executed'; \
fi && \
echo '$#: done'
$ make a
a: MY_VAR is whatever
a: should be executed
a: done
$ make b
b: MY_VAR is
b: done
$ make b a
b: MY_VAR is
b: done
a: MY_VAR is whatever
a: should be executed
a: done
It may look extremely strange but it guarantees that MY_VAR is computed if and only if targets a or b are invoked, and only at most once for each. Have a look at MadScientist's post for detailed explanations. Go, it's brilliant.
The ifeq and family of conditionals are evaluated when parsing the Makefile. If you want a conditional for a Make variable when expanding a rule, you'll want to use the $(if ) function:
.PHONY: a b
a b:
#$(if $(strip $(MY_VAR)),echo "MY_VAR isn't empty",)
#echo done
a: MY_VAR =
b: MY_VAR = something
A bit left field, but was useful in my case so I guess it's worth a share: pipe the value to xargs with the --no-run-if-empty option:
echo $(POSSIBLY_EMPTY) | xargs --no-run-if-empty echo
Note that this will not work on OSX. See How to ignore xargs commands if stdin input is empty? for more details on xargs --no-run-if-empty

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

How To test the exit status, and do something in Makefile

I'm trying to do this ..... and this is how my Makefile look like
.PHONY: run
SHELL := /bin/tcsh
run:
md5sum -c md; \
if ($$?==0) then \
echo "PASS" \
else \
echo "FAIL" \
endif
But i got this error.
if: Badly formed number.
make: *** [run] Error 1
Is what I'm doing correct? Or is there a better way of doing that in a Makefile?
Basically, you should simply not, ever, use csh (or tcsh) for writing makefile rules. Write the rule using POSIX shell:
.PHONY: run
run:
md5sum -c md; \
if [ $$? -eq 0 ]; then \
echo "PASS"; \
else \
echo "FAIL"; \
fi

Resources