How to have multiple (non-file) dependencies in a Makefile - bash

I have a Makefile of the following form:
upload_image_local: build_image_local ; echo "This gets printed" ; upload_image
with
build_image_local: echo "This is build_image_local"
./my_shell_script_1.sh $(SOME_ENV_VAR_1)
upload_image: echo "This is upload_image"
./my_shell_script_2.sh $(SOME_ENV_VAR_2)
As you can tell, when I run make upload_image_local, I want build_image_local and upload_image to be run. Instead I get:
/bin/sh: upload_image: command not found
I know for file dependencies we put spaces between them, but here I'm not sure how to separate the two statements properly. I also tried putting them in the next line with semicolons and a tab character (I know it matters):
upload_image_local:
build_image_local ; echo "This gets printed" ; upload_image
In which case I get:
/bin/sh: build_image_local: command not found
make: *** [upload_image_local] Error 127
What is the correct way to run this target? Also, why do the echo commands get printed fine? If it matters I'm running this Makefile on a Mac and with the sh shell (as it says).

I would really advise you to take a tutorial on Make.
GNU make manual
Quickstart
To achieve the desired effect, I think the following should work:
all: upload_image
build_image_local:
echo "This is build_image_local"
./my_shell_script_1.sh $$SOME_ENV_VAR_1
upload_image_local: build_image_local
echo "This gets printed"
upload_image: upload_image_local
echo "This is upload_image"
./my_shell_script_2.sh $$SOME_ENV_VAR_2
Also, I am assuming SOME_ENV_VAR_1 and SOME_ENV_VAR_2 are shell variables which is why I wrote them as $$SOME_ENV_VAR_1 and $$SOME_ENV_VAR_2. If they are variables of your Makefile then turn them back to way they were.
Also remember that the recipe in Makefile would spawn a subshell and you need to make sure that your env vars are available to those subshells.

Related

why ‘&&’ disables errexit(set -e)?

consider script here:
set -e
make && make install
echo "SHOULD NOT BE HERE"
I expect that if make fails, the script will be aborted, but it's not:
make: *** No targets specified and no makefile found. Stop.
SHOULD NOT BE HERE
But, if I changed it like this:
set -e
make
make install
echo "SHOULD NOT BE HERE"
It works as expected:
make: *** No targets specified and no makefile found. Stop.
Why this happens?
Due to make && make install is commonly used in my build script, how should I use it correctly?
And please DO NOT link this question to Using set -e / set +e in bash with functions, it's not the same question.
Quoting from the answer to the question you linked:
Quoting sh(1) from FreeBSD, which explains this better than bash's man page:
-e errexit
Exit immediately if any untested command fails in non-interactive
mode. The exit status of a command is considered to be explicitly
tested if the command is part of the list used to control an if,
elif, while, or until; if the command is the left hand operand of
an “&&” or “||” operator; or if the command is a pipeline preceded
by the ! operator. If a shell function is executed and its exit
status is explicitly tested, all commands of the function are con‐
sidered to be tested as well.
errexit exits only if an untested command fails. Using && (or ||) will means that bash considers the command to the left of && to be explicitly tested (which in turn means that it will not be handled by errexit).
See also here (specifically the part about list constructs).
As far a I know, there is no way to achieve what you would like by setting bash options.

GNU Make and using ; to execute multiple shell commands in the same command shell

I have a very basic problem using GNU Make 3.81 on Windows, I must be doing something very silly and I'm sure someone here will point it out in milliseconds. My problem is with using ";" to run multiple commands in the same shell.
As I understand it, make runs each line in its own command shell and so if you want to run two commands, one after the other, you must put them on the same line separated by a semicolon. In it's simplest form:
all:
echo hello; echo hello
...should produce the output:
hello
hello
But for me it produces the output:
hello; echo hello
In other words, the semicolon is being passed straight through to the shell, which doesn't make too much sense for cmd.exe.
I'm now ready to be embarrassed by everyone pointing out where I've gone wrong...
FYI, the reason I need this is that I'm using a $(foreach) loop which must execute two shell commands for each iteration.
You are be under the impression that ; is a GNU-make operator for executing multiple
commands in the same shell within a recipe. Not so. It is linux shell operator
for punctuating a sequence of commands on the same line. It is not an operator for
the Windows shell, cmd, so when the recipe:
echo hello; echo hello
is executed by make on Linux, it has the output you expect, but when executed by make
on Windows it just means echo this:
hello; echo hello
So, the answer is that your shell is the thing that has to understand that ; separates multiple commands on the same line, it's nothing to do with make. This is not the case for Windows cmd.exe but is presumably the case for the shells that normally arrive with environments that use make (Linux, msys etc.). In my case, a good workaround was this:
define useDef
echo hello
echo hello
endef
all:
$(call useDef)
With this form of "single-lined" definition I can invoke a multiline shell command inside $(foreach). Make still does each "hello" in its own shell but in my case that's OK because I'm appending outputs to a file. If you need two commands to be run in the same shell for some reason then, on Windows, you would need to write a separate batch file (which I suppose you could create from inside the makefile).
I know this question is relatively old, but I've stumbled across the same problem recently. The solution (for me) was quite simple. I replaced ; with &.
Basically
all:
echo hello & echo hello
will produce
hello
hello
in cmd.exe.
And it works with $(foreach) loops as well.
UPD: You also can use && instead of & if you don't want your commands to fail silently.

I want to echo something at compile time.But it doesn't work for a cross compiler

I am working on a cross-compiler. I tried:
all:
; $(info $$var is [${var}])echo Hello world
But nothing is printed after running
$ make install
Instead a new file Makefile~ is created.
What should I do?
Try using #echo in the Makefile, which will suppress the echoing line and discards '#' before sending the line to the shell.
Typically you would use this for a command whose only effect is to print something, such as an echo command to indicate progress through the makefile.

Execute a line in Bash without aborting if it fails?

Is there a generic way in a bash script to "try" something but continue if it fails? The analogue in other languages would be wrapping it in a try/catch and ignoring the exception.
Specifically I am trying to source an optional satellite script file:
. $OPTIONAL_PATH
But when executing this, if $OPTIONAL_PATH doesn't exist, the whole script screeches to a halt.
I realize I could check to see if the file exists before sourcing it, but I'm curious if there is a generic reusable mechanism I can use that will ignore the error without halting.
Update: Apparently this is not normal behavior. I'm not sure why this is happening. I'm not explicitly calling set -e anywhere ($- is hB), yet it halts on the error. Here is the output I see:
./script.sh: line 36: projects/mobile.sh: No such file or directory
I added an echo "test" immediately after the source line, but it never prints, so it's not anything after that line that is exiting. I am running Mac OS 10.9.
Update 2: Nevermind, it was indeed shebanged as #!/bin/sh instead of #!/bin/bash. Thanks for the informative answer, Kaz.
Failed commands do not abort the script unless you explicitly configure that mode with set -e.
With regard to Bash's dot command, things are tricky. If we invoke bash as /bin/sh then it bails the script if the . command does not find the file. If we invoke bash as /bin/bash then it doesn't fail!
$ cat source.sh
#!/bin/sh
. nonexistent
echo here
$ ./source.sh
./source.sh: 3: .: nonexistent: not found
$ ed source.sh
35
1s/sh/bash/
wq
37
$ ./source.sh
./source.sh: line 3: nonexistent: No such file or directory
here
It does respond to set -e; if we have #!/bin/bash, and use set -e, then the echo is not reached. So one solution is to invoke bash this way.
If you want to keep the script maximally portable, it looks like you have to do the test.
The behavior of the dot command aborting the script is required by POSIX. Search for the "dot" keyword here. Quote:
If no readable file is found, a non-interactive shell shall abort; an interactive shell shall write a diagnostic message to standard error, but this condition shall not be considered a syntax error.
Arguably, this is the right thing to do, because dot is used for including pieces of the script. How can the script continue when a whole chunk of it has not been found?
Otherwise arguably, this is braindamaged behavior inconsistent with the treatment of other commands, and so Bash makes it consistent in its non-POSIX-conforming mode. If programmers want a command to fail, they can use set -e.
I tend to agree with Bash. The POSIX behavior is actually more broken than initially meets the eye, because this also doesn't work the way you want:
if . nonexistent ; then
echo loaded
fi
Even if the command is tested, it still aborts the script when it bails.
Thank GNU-deness we have alternative utilities, with source code.
You have several options:
Make sure set -e wasn't used, or turn it off with set +e. Your bash script should not exit by default simply because the . command failed.
Test that the file exists prior to sourcing.
[ -f "$OPTIONAL_PATH" ] && . "$OPTIONAL_PATH"
This option is complicated by the fact that if $OPTIONAL_PATH does not contain
any slashes, . will still try to find the file in your path.
If you want to keep set -e on, "hide" the failure like this:
. "$OPTIONAL_PATH" || true
Even if the source fails, the exit status of the command list as a whole will be 0, due to the || true.
(Much of this is covered [better] by Kaz's answer, especially the references to the POSIX standard, but I wasn't sure when or if he would undelete his answer.)
This is not the default behavior. Did you set -e or use #!/bin/bash -e anywhere in your script, to make it automatically exit on failure?
If so, you can use
. $OPTIONAL_PATH || true
to continue anyways.

Trouble Passing Parameter into Simple ShellScript (command not found)

I am trying to write a simple shell-script that prints out the first parameter if there is one and prints "none" if it doesn't. The script is called test.sh
if [$1 = ""]
then
echo "none"
else
echo $1
fi
If I run the script without a parameter everything works. However if I run this command source test.sh -test, I get this error -bash: [test: command not found before the script continues on and correctly echos test. What am I doing wrong?
you need spaces before/after '[',']' chars, i.e.
if [ "$1" = "" ] ; then
#---^---------^ here
echo "none"
else
echo "$1"
fi
And you need to wrap your reference (really all references) to $1 with quotes as edited above.
After you fix that, you may also need to give a relative path to your script, i.e.
source ./test.sh -test
#------^^--- there
When you get a shell error message has you have here, it almost always helps to turn on shell debugging with set -vx before the lines that are causing your trouble, OR very near the top your script. Then you can see each line/block of code that is being executed, AND the value of the variables that the shell is using.
I hope this helps.

Resources