I have a makefile rule that generates a C file based on the content of a list.
LIST = foo \
bar
generated.c:
#echo "Creating $#"
(echo "#include <stuff.h>"; \
echo "void init_local() {"; \
for i in $(LIST) ; do \
echo " init_$$i();"; \
done;
echo "}") > $#
On a Linux platform, this gives me the result I expect:
#include <stuff.h>
void init_local() {
init_foo();
init_bar();
}
However, on my windows 7 64-bit platform with GNU Make 3.82 (x86_64-w64-mingw32),
I get the following (all on one line):
"#include <stuff.h>"; \"void init_local() {" \
for i in foo bar; do echo " init_$i();"; done; echo "}"
I have also tried to break out the rules for individual lines:
echo "#include <stuff.h>" > %#
echo "void init_local() {" >> $#
...
With that solution, I have 1 string per line, but the quotes are still there!
How would I change the rule to get the generated file correct on both the Linux and Windows platform?
This is because DOS has own command separator - & (similar to ; in bash). But I would suggest using make echo instead of shell echo that will work in DOS and in bash:
But this is not all, as I understand you don't need quotes ("") in output, that is why you shouldn't pass them to echo, but instead escape special symbols with ^.
ifeq ($(OS_TYPE),linux)
Q="
ESC=
else
Q=
ESC=^
endif
generated.c:
#echo "Creating $#"
echo $(Q)#include $(ESC)<stuff.h$(ESC)>$(Q) > $#
echo $(Q)void init_local() {$(Q) >> $#
...
In addition, your bash for..;do..;done; loop should be replaced with make $(foreach, $(LIST), echo ...) to be correct for both shells.
Yes, I know, this is becoming look ugly, but that is because of weird DOS rules. If you need to generate a lot I would suggest using awk, python or perl.
Related
I have the following code from a make file, I know this creates bin folder in Home if that doesn't exist... but I couldn't understand what $$HOME/bin mean...
I googled and found $$ is to get the processid of the bash... but couldn't understand what $$HOME/bin mean... can someone please explain ?
.PHONY: home_bin
home_bin: ## Create home bin if not created
# if [[ ! -d "$$HOME/bin" ]]; then \
echo "Creating $$HOME/bin"; \
mkdir $$HOME/bin; \
echo "✔︎ $$HOME/bin created"; \
else \
echo "✔︎ $$HOME/bin already created"; \
fi
Thank you.
make itself performs expansion of $-prefixed characters; the $$ is expanded to a single literal $ to pass to the shell.
Consider a simple Makefile:
x=f
all:
xoo=3 && echo $xoo
which will output foo, because
make expands $x to the single character f.
make passes the string xoo=3 && echo foo to the shell for execution
Compare with
x=f
all:
xoo=3 && echo $$xoo
which outputs 3, because
make expands $$ to $
make passes the string xoo=3 && echo $xoo to the shell for execution
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
If there a way to affect the '?' variable in bash?
For a regular variable, it is possible to just use FOO=bar, thus 'echo $FOO' will output bar, but for some reason, it is not working with the '?' variable.
I have found two workaround but they are quite unsatisfactory.
First, it is possible to use true and false that will set $? to respectively 0 and 1.
#! /bin/bash
echo $?
true
echo $?
false
echo $?
This will output xxx, 0, 1. This workaround is limited, because it only allow to affect the values 0 and 1.
Then, it is possible to write some code in C (or other) that will just return the value in parameter via exit and then call this function. Example :
#! /bin/bash
rm foo.c
touch foo.c
echo "#include <stdio.h>" >> foo.c
echo "#include <stdlib.h>" >> foo.c
echo "int main(int argc, char **argv)" >> foo.c
echo "{" >> foo.c
echo " return atoi(argv[1]);" >> foo.c
echo "}" >> foo.c
gcc -o foo foo.c
./foo 42
echo $?
That will output 42. Even though it works, it is pretty nasty for doing something so simple, not to mention that this is only a simplified version without all the checkings that would have to be done in order to be sure not to overwrite anything. In addition, this require gcc to be present on the system.
There's no need to involve C at all if you just want to set the most recent return code (although I'm wondering why you're trying to do this). A simple shell function is sufficient:
ret() { return $1; }
Call it like ret 42.
Of course you know that '$?' holds the exit code of the last executed command. If the command is your own written executable, then '$?' holds its exit code.
The default value is 0 (success).
To sum up: use exit in your own scripts.
$ ls
$ echo $?
0
$ echo '#!/bin.bash' > script1.sh && chmod u+x script1.sh
$ ./script1.sh
$ echo $?
0
$ echo -e '#!/bin.bash\nexit 42' > script2.sh && chmod u+x script2.sh
$ ./script2.sh
$ echo $?
42
Also, have a look at Return value in bash script
I want to create a multiline file with Make, having exact content:
#!/bin/bash
if [ "$JAVA_HOME" = "" ]; then echo "Please set JAVA_HOME"; exit 1; fi
export CONFIG_VARS=$( cat <<EOF
-Dmapred.job.tracker=$JT
EOF
)
${HADOOP_HOME}/bin/hadoop $1 $HADOOP_CONFIG_VARS ${*:2} 2>&1 | grep -v SLF4J
How can I tell make to output a file with this exact content somewhere?
I tried this:
define SCRIPT_CONTENT
#!/bin/bash
if [ "$JAVA_HOME" = "" ]; then echo "Please set JAVA_HOME"; exit 1; fi
export CONFIG_VARS=$( cat <<EOF
-Dmapred.job.tracker=$JT
EOF
)
${HADOOP_HOME}/bin/hadoop $1 $HADOOP_CONFIG_VARS ${*:2} 2>&1 | grep -v SLF4J
endef
export SCRIPT_CONTENT
bin/script:
#echo "$$SCRIPT_CONTENT" > bin/script
This paricular solution 1) wipes $ and first char after $-es and 2) is ugly because the definition should happen outside of the particular target where it's needed :(
I also tried this:
bin/script:
#echo '
#!/bin/bash
if [ "$JAVA_HOME" = "" ]; then echo "Please set JAVA_HOME"; exit 1; fi
export CONFIG_VARS=$( cat <<EOF
-Dmapred.job.tracker=$JT
EOF
)
${HADOOP_HOME}/bin/hadoop $1 $HADOOP_CONFIG_VARS ${*:2} 2>&1 | grep -v SLF4J
' > bin/script
This returns error when in make, works outside of make...
Any suggestion is very welcome!
Make wants any $ characters that should be reproduced literally to be escaped by inserting another $ in front of them.
More broadly, though, it seems like you're trying to use Make as a shell-script replacement. The more idomatic way to do this would be to put that content in a source file that you can copy to the destination, or to put it in a script that will write it into a specified destination. The Makefile then just has to invoke the copy command or the script.
With the help from this magnificent answer, I cooked up the following.
# From https://stackoverflow.com/a/8316519/874188
define \n
endef
define SCRIPT_CONTENT
#!/bin/bash
if [ "$$JAVA_HOME" = "" ]; then echo "Please set JAVA_HOME"; exit 1; fi
export CONFIG_VARS=$$( cat <<EOF
-Dmapred.job.tracker=$$JT
EOF
)
$${HADOOP_HOME}/bin/hadoop $$1 $$HADOOP_CONFIG_VARS $${*:2} 2>&1 | grep -v SLF4J
endef
bin/script:
echo '$(subst $(\n),\n,$(SCRIPT_CONTENT))' >$#
When testing, I found that I needed to have a semicolon at the end of the echo line when it didn't have any redirection. I can speculate that there is a built-in echo which gets invoked when there are no shell metacharacters in the command line?
Also, notice that the definition cannot contain any single quotes, and that all dollar signs have to be doubled. Maybe one or the other of these restrictions could be removed; I was unsuccessful, but then I didn't spend too much time or effort.
You cannot do this in make. Beyond what Novelocrat says regarding $, there's the fact that make is line-oriented and does not have any ability to generate a command that contains a newline character. All newlines that appear unescaped (without a backslash before them) are parsed by make as ending that recipe line, and each recipe line is sent to a different invocation of the shell. If you want the entire command to be sent as a single string to the same shell, then you must escape the newlines.
However, make will remove all backslash/newline pairs before it runs the command.
The only possible way to do this completely within make is to generate the file one line at a time, like this:
bin/script:
#echo '#!/bin/bash' > $#
#echo 'if [ "$$JAVA_HOME" = "" ]; then echo "Please set JAVA_HOME"; exit 1; fi' >> $#
#echo 'export CONFIG_VARS=$$( cat <<EOF' >> $#
#echo ' -Dmapred.job.tracker=$$JT' >> $#
#echo 'EOF' >> $#
#echo ')' >> $#
#echo '$${HADOOP_HOME}/bin/hadoop $$1 $$HADOOP_CONFIG_VARS $${*:2} 2>&1 | grep -v SLF4J' >> $#
As Novelocrat says, the typical way this is done is to have the script file as a separate file and copy it where you want it, rather than generating it.
I am searching for a way to get the output and the exit code of a command to variables in a makefile.
Basicly I want this: (bash)
output=$(whoami)
returnval=$?
echo "OUT:"
echo $output
echo "RET:"
echo $returnval
to be in a makefile
note: should work in a rule section
Thanks
EDIT: SOLVED
$(eval OUTPUT_RC="$(shell whoami;echo $$?)")
$(eval OUTPUT="$(shell echo $(OUTPUT_RC) | sed -e "s/^\(.*\)\(.\{2\}\)$$/\1/")")
$(eval RC="$(shell echo $(OUTPUT_RC) | sed -e "s/^.*\(.\)$$/\1/")")
echo $(OUTPUT)
echo $(RC)
If you are going to use eval anyway, make it print things you can eval directly.
$(eval $(shell echo OUTPUT_RC=`whoami`; echo $$?))
OUTPUT=$(word 1,$(OUTPUT_RC))
RC=$(word 2,$(OUTPUT_RC))
GNU make offers the shell function, as in:
OUTPUT=$(shell whoami)
OUTPUT_RC=$(shell whoami; echo $$?)