Behavior of parenthesis in the shell invoked from make - shell

I'm building a Linux from scratch on my old Android phone in a chrooted environment (which can be part of the problem), on a mounted ext4 partition on the sd card. I managed to cross compile install most of the stuff I need to build locally there. I installed the latest release of everything.
And now time to make something in place without cross compilation.
I decided to make and install inetutils-1.9.4.
The configure runs just fine, but when I try to make, it just fails without saying a thing:
# make
make all-recursive
make[1]: Entering directory '/tmp/inetutils_build'
Making all in lib
make[1]: *** [Makefile:1491: all-recursive] Error 1
make[1]: Leaving directory '/tmp/inetutils_build'
make: *** [Makefile:1428: all] Error 2
So I looked at the generated makefile and it contains this rule:
$(am__recursive_targets):
#fail=; \
if $(am__make_keepgoing); then \
failcom='fail=yes'; \
else \
failcom='exit 1'; \
fi; \
dot_seen=no; \
target=`echo $# | sed s/-recursive//`; \
case "$#" in \
distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
*) list='$(SUBDIRS)' ;; \
esac; \
for subdir in $$list; do \
echo "Making $$target in $$subdir"; \
if test "$$subdir" = "."; then \
dot_seen=yes; \
local_target="$$target-am"; \
else \
local_target="$$target"; \
fi; \
($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
|| eval $$failcom; \
done; \
if test "$$dot_seen" = "no"; then \
$(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
fi; test -z "$$fail"
After inserting some echos I have found that the line with the $(am__cd)... fails, and the ored expression runs which exits the script with failure. So I attempted to find out which command fails there, and inserted some echos within the parenthesis, like this:
(echo "1" && $(am__cd) $$subdir && echo "2" && $(MAKE) $(AM_MAKEFLAGS) $$local_target && echo "3") \
|| eval $$failcom; \
Now it prints:
make all-recursive
make[1]: Entering directory '/tmp/inetutils_build'
Making all in lib
1
make[1]: *** [Makefile:1491: all-recursive] Error 1
make[1]: Leaving directory '/tmp/inetutils_build'
make: *** [Makefile:1428: all] Error 2
However I wanted to make sure the echo doesn't fail, so I added another one.
(echo "1" && echo "1b" && $(am__cd) $$subdir && echo "2" && $(MAKE) $(AM_MAKEFLAGS) $$local_target && echo "3") \
|| eval $$failcom; \
When I tried it. It again prints just 1.
I didn't give it up just yet.
I wanted to make sure echo really fails within the script.
So I tried adding new statements into the script, before the problematic command:
(echo "A" && echo "B"); \
(echo "X" || echo "Y"); \
echo "N" && echo "M"; \
(echo "C" && echo "D"); \
It prints:
A
X
Y
N
M
C
D
Then it prints
1
1b
2
and starts building!
I'm totally puzzled here. Why does the first two parenthesized expressions fail, but not the last one?
In fact even writing (true) || echo "ooops" fails at first, but not when I paste it below the ored X Y parenthesis. Apparently the ored parenthesis "fixes" it, whatever state it got stuck on.
What is going on?
It should be noted I cannot reproduce this at the shell prompt but only the statements inserted into the makefile does this.
EDIT:
By removing the # from the beginning it echoes back the command but nothing else, no errors:
fail=; \
if (target_option=k; case ${target_option-} in ?) ;; *) echo "am__make_running_with_option: internal error: invalid" "target option '${target_option-}' specified" >&2; exit 1;; esac; has_opt=no; sane_makeflags=$MAKEFLAGS; if { if test -z '1'; then false; elif test -n 'arm-unknown-linux-gnueabi'; then true; elif test -n '4.2' && test -n '/tmp/inetutils_build'; then true; else false; fi; }; then sane_makeflags=$MFLAGS; else case $MAKEFLAGS in *\\[\ \ ]*) bs=\\; sane_makeflags=`printf '%s\n' "$MAKEFLAGS" | sed "s/$bs$bs[$bs $bs ]*//g"`;; esac; fi; skip_next=no; strip_trailopt () { flg=`printf '%s\n' "$flg" | sed "s/$1.*$//"`; }; for flg in $sane_makeflags; do test $skip_next = yes && { skip_next=no; continue; }; case $flg in *=*|--*) continue;; -*I) strip_trailopt 'I'; skip_next=yes;; -*I?*) strip_trailopt 'I';; -*O) strip_trailopt 'O'; skip_next=yes;; -*O?*) strip_trailopt 'O';; -*l) strip_trailopt 'l'; skip_next=yes;; -*l?*) strip_trailopt 'l';; -[dEDm]) skip_next=yes;; -[JT]) skip_next=yes;; esac; case $flg in *$target_option*) has_opt=yes; break;; esac; done; test $has_opt = yes); then \
failcom='fail=yes'; \
else \
failcom='exit 1'; \
fi; \
dot_seen=no; \
target=`echo all-recursive | sed s/-recursive//`; \
case "all-recursive" in \
distclean-* | maintainer-clean-*) list='lib libinetutils libtelnet libicmp libls src telnet telnetd ftp ftpd talk talkd whois ping ifconfig doc man tests' ;; \
*) list='lib libinetutils libtelnet libicmp libls src telnet telnetd ftp ftpd talk talkd whois ping ifconfig doc man tests' ;; \
esac; \
for subdir in $list; do \
echo "Making $target in $subdir"; \
if test "$subdir" = "."; then \
dot_seen=yes; \
local_target="$target-am"; \
else \
local_target="$target"; \
fi; \
(CDPATH="${ZSH_VERSION+.}:" && cd $subdir && make $local_target) \
|| eval $failcom; \
done; \
if test "$dot_seen" = "no"; then \
make "$target-am" || exit 1; \
fi; test -z "$fail"
EDIT 2:
Thanks for the tips, I added set -x to the beginning of the rule. These are the commands:
+ fail=
+ target_option=k
+ case ${target_option-} in
+ has_opt=no
+ sane_makeflags=w
+ test -z 1
+ test -n arm-unknown-linux-gnueabi
+ true
+ sane_makeflags=-w
+ skip_next=no
+ for flg in $sane_makeflags
+ test no = yes
+ case $flg in
+ case $flg in
+ test no = yes
+ failcom='exit 1'
+ dot_seen=no
++ echo all-recursive
++ sed s/-recursive//
+ target=all
+ case "all-recursive" in
+ list='lib libinetutils libtelnet libicmp libls src telnet telnetd ftp ftpd talk talkd whois ping ifconfig doc man tests'
+ for subdir in $list
+ echo 'Making all in lib'
Making all in lib
+ test lib = .
+ local_target=all
+ CDPATH=:
+ eval exit 1
++ exit 1

Did you put SHELL := /bin/bash at the top of your makefile? Otherwise, make(1) tends to invoke /bin/sh as the shell, which has very different properties.

Related

GNU RISCV GCC Makefile.in all target recipe

RISCV-GCC Makefile.in
# The target built for a native non-bootstrap build.
all:
#if gcc-bootstrap
[ -f stage_final ] || echo stage3 > stage_final
#r=`${PWD_COMMAND}`; export r; \
s=`cd $(srcdir); ${PWD_COMMAND}`; export s; \
$(MAKE) $(RECURSE_FLAGS_TO_PASS) `cat stage_final`-bubble
#endif gcc-bootstrap
#: $(MAKE); $(unstage)
#r=`${PWD_COMMAND}`; export r; \
s=`cd $(srcdir); ${PWD_COMMAND}`; export s; \
#if gcc-bootstrap
if [ -f stage_last ]; then \
TFLAGS="$(STAGE$(shell test ! -f stage_last || sed s,^stage,, stage_last)_TFLAGS)"; \
$(MAKE) $(TARGET_FLAGS_TO_PASS) all-host all-target; \
else \
#endif gcc-bootstrap
$(MAKE) $(RECURSE_FLAGS_TO_PASS) all-host all-target \
#if gcc-bootstrap
; \
fi \
#endif gcc-bootstrap
&& :
What does this statement, #: $(MAKE); $(unstage), do in this recipe for target all?
I have tried to remove other statements but this one, and make this Makefile, it does nothing. However, according to the mechanism of make, it should invoke the recipe for first target if without dependencies, therefore it will spawn a new shell process to call make in current directory, which may lead to a indefinitively recursive call. But it just does nothing and terminate.
I am confused about what that statement, #: $(MAKE); $(unstage) do, and why need it there in recipe.

sh conditionally pass an option to command

I want to do something like:
#!/bin/sh
[ -f "/tmp/nodes" ]
[[ $? -eq 0 ]] && VAL=$? ||
geth --datadir /root/.ethereum \
${VAL+"--nodekey \"/root/nodekey.txt\""} \
--networkid 1999 \
--rpc \
--rpcaddr "0.0.0.0" \
I want the option --nodekey "/root/nodekey.txt" to be passed if the file /tmp/nodes exists. How can that be done more elegantly than an if with two nearly identical commands?
--EDIT--
This is the best I've been able to get working so far:
if [ $VAL -eq 0 ]; then
/geth --datadir /root/.ethereum \
--nodekey "/root/nodekey.txt" \
# No dice
# Would be nice if this worked so I didn't need the if
# ${VAL+ --nodekey "/root/nodekey.txt" } \
--networkid 1999 \
--rpc \
--rpcaddr "0.0.0.0"
else
/geth --datadir /root/.ethereum \
--networkid 1999 \
--rpc \
--rpcaddr "0.0.0.0" \
fi
This is another line in the file and works fine:
ENODE_URL=$(/geth --datadir /root/.ethereum ${VAL+ --nodekey "/root/nodekey.txt"} --exec "${JS}" console 2>/dev/null | sed -e 's/^"\(.*\)"$/\1/')
There's a bashism here, but it's [[ $? -eq 0 ]], as [[ is a ksh extension adopted by bash. There's no point to using $? at all here, since you can just directly perform your assignment based on whether the test -f succeeds:
touch /tmp/nodes # set us up for the truthy path
if test -f /tmp/nodes; then tmp_nodes_exists=1; else unset tmp_nodes_exists; fi
printf '%s\n' /tmp/nodes ${tmp_nodes_exists+"REALLY EXISTS" "(yes, really)"}
...properly emits as output (as run with dash, perhaps the most common minimal /bin/sh interpreter):
/tmp/nodes
REALLY EXISTS
(yes, really)
By contrast, to demonstrate that the other path fails as it should:
rm -f -- /tmp/nodes # set us up for the falsey path
if test -f /tmp/nodes; then tmp_nodes_exists=1; else unset tmp_nodes_exists; fi
printf '%s\n' /tmp/nodes ${tmp_nodes_exists+"REALLY EXISTS" "(yes, really)"}
emits as output only:
/tmp/nodes

Makefile multiline dash command run executable in a detached process

I have the following target in my makefile: (I'd like to run python http server in a detached process and when bash script is done kill the server)
TEST_PORT = 17777
test::
$(ENV_VARS) \
python -m SimpleHTTPServer $(TEST_PORT); \
PID=$$(lsof -t -i #localhost:$(TEST_PORT) -sTCP:listen); \
echo $(PID); \
if [ -n "$$PID" ]; \
then \
python test.py; \
fi; \
function finish { \
if [ -n "$$PID" ]; \
then \
kill -9 $$PID; \
fi \
} \
trap finish EXIT;
However when I put a & after the line python ... I get an error
/bin/dash: Syntax error: ";" unexpected
How can this be done in a proper way?
EDIT
I have changed my makefile to do the following:
test::
python -m SimpleHTTPServer $(TEST_PORT) &
PID=$$(lsof -t -i #localhost:$(TEST_PORT) -sTCP:listen); \
if [ -n "$$PID" ]; \
then \
$(ENV_VARS) python test.py; \
fi \
function finish { \
if [ -n "$$PID" ]; \
then \
kill -9 $$PID; \
fi \
} \
echo $$PID; \
trap finish EXIT;
However I am getting an error: (without the line number)
/bin/dash: Syntax error: word unexpected
The important thing to remember here is that your line breaks don't actually exist when the shell sees the command.
So your first command becomes:
$(ENV_VARS) python -m SimpleHTTPServer $(TEST_PORT); PID=$$(lsof -t -i #localhost:$(TEST_PORT) -sTCP:listen); echo $(PID); if [ -n "$$PID" ]; then python test.py; fi; function finish { if [ -n "$$PID" ]; then kill -9 $$PID; fi } trap finish EXIT;
And your second command becomes:
PID=$$(lsof -t -i #localhost:$(TEST_PORT) -sTCP:listen); if [ -n "$$PID" ]; then $(ENV_VARS) python test.py; fi function finish { if [ -n "$$PID" ]; then kill -9 $$PID; fi } echo $$PID; trap finish EXIT;
Now those are both very hard to read so I don't expect you to spot the problem but the problem is that you are missing statement terminators in a few places.
Specifically:
Braces ({}) are word elements and so need spaces around them (and a terminator before, and after, the closing brace). You are missing those terminators here fi } trap and here fi } echo.
fi is also not a statement terminator and so it needs one between it and the next statement. You are missing one here test.py; fi function (as well as the ones in the braces from the first point).

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

How to make makefile exit with error when using a loop?

If I have the following bash command:
for i in ./ x ; do ls $i ; done && echo OK
"ls ./" is executed, and then "ls x", which fails (x is missing) and OK is not printed.
If
for i in x ./ ; do ls $i ; done && echo OK
then even though "ls x" fails, because the last statement in the for loop succeeded, then OK is printed. This is a problem when using shell for loops in makefiles:
x:
for i in $(LIST) ; do \
cmd $$i ;\
done
How can I make make fail if any of the individual executions of cmd fails?
Use the break command to terminate the loop when a command fails
x:
for i in $(LIST) ; do \
cmd $$i || break ;\
done
That won't make the makefile abort, though. You could instead use exit with a non-zero code:
x:
for i in $(LIST) ; do \
cmd $$i || exit 1 ;\
done
After executing the command, check for return value of that command using $?, as its make file you need to use double $. If its non zero, then exit with failure.
x:
set -e
for i in $(LIST); do \
cmd $$i; \
[[ $$? != 0 ]] && exit -1; \
echo 'done'; \
done

Resources