Make doesn't finish when run with entr - makefile

I'm trying to use entr to recompile as soon as I change a C file with the following command:
$ echo ex8.c | entr make ex8 && ./ex8
When I run it I get the cc output but then nothing happends
$ echo ex8.c | entr make ex8 && ./ex8
cc -Wall -g ex8.c -o ex8
If I just write it manually it works great
$ make ex8 && ./ex8
How should I write it with entr for it to work?

The man page wasn't quite detailed enough, but I installed it and tried it. Note your command is actually three distinct shell commands: echo ex8.c, entr make ex8, and ./ex8. These are connected by a pipe (the first two) and the && operator (the final two). The two commands in the pipeline are both started together in parallel. The final command will not be invoked until the pipeline completes, then if the exit code is success it be run.
This means that the final command ./ex8 will not be started until after the entr make ex8 command finishes. But, entr does not exit after it runs make one time: its entire point is to continue to watch the source file and run make every time it changes. That's why the final command is never invoked: entr never exits.
There are multiple ways to fix this but the simplest way is to add a rule to your makefile that will build AND RUN the command, then call that with make; add this to your makefile:
.PHONY: run-ex8
run-ex8: ex8
./$<
Now use entr like this:
echo ex8.c | entr make run-ex8

Related

Insert multiline to file inside docker container from outside

I need to save multiline content about xdebug from makefile, but I cannot use file in repo, previosuly I had do this within:
docker exec -i $(CONTAINER) bash -c 'cat > /etc/php8/conf.d/50_xdebug.ini' < file
But now I need to use variable (multiline) or insert line by line.
xdebug: check-xdebug-env
ifeq ($(XDEBUG_STATUS),1)
#make xdebug-enable
else
#make xdebug-disable
endif
xdebug-disable:
#make restart
xdebug-enable:
#make restart
restart:
#docker exec $(CONTAINER) bash -c "kill -9 $(shell $(CMD) ps -a | grep '[p]hp-fpm' | awk '{print $$1}')"
Rule #1 for debugging make: remove ALL the # prefixes to your recipe lines. Rule #2: examine the recipe lines make prints carefully and make sure they're the same thing you would run if you were running it by hand. Anywhere they are different, that's where you have to concentrate on fixing something.
Tip for writing makefiles: if you're using the $(shell ...) make function inside a recipe, you're almost certainly doing something wrong.
I don't understand what you want to accomplish here and you've left out important parts in your question (what does $(CMD) expand to, for example) but remember that make functions, like $(shell ...), are all expanded by make before it invokes any command in the recipe. So this script is run on your host system, before make invokes the docker command. It's not run inside the container. That's probably not what you want.
restart:
docker exec $(CONTAINER) bash -c 'kill -9 $$($(CMD) ps -a | grep "[p]hp-fpm" | awk "{print \$$1}")'

'make run clean' shell script that properly handles interupts

My first jab at writing a shell script that would run my makefile, run the executable, and then remove the executable looked like:
make
if [ -e a.out ]; then
./a.out
rm a.out
fi
after realizing that the script didn't clean up a.out when I pressed ^C, I tried modifying it like so:
onintr foo
make
if [ -e a.out ]; then
./a.out
rm a.out
fi
foo:
rm a.out
but it did not solve my problem. If any of you know how I can accomplish what I'm trying to do that would be very helpful.
Add an infinite loop at the top of your script, print out a line in your onintr routine and test that you hit it when you break the loop with Ctrl-C. Remove the extra ouput and the infinite loop. Read your man pages on make, you probably don't have much control over what happens when it gets interrupted. That's why so many make files have a Clean target in them, so you can rerun make to clean-up all of its outputs.

Chisel installation error

While following the tutorial on the Chisel official website for installation, I came to the point where I should test if the installation was done correctly. Doing so yields this error:
set -e -o pipefail; "sbt" -Dsbt.log.noformat=true -DchiselVersion="2.+" "run Parity --genHarness --compile --test --backend c --vcd --targetDir /home/me/chisel-tutorial/generated/examples " | tee /home/me/chisel-tutorial/generated/examples/Parity.out
/bin/bash: sbt: command not found
make: *** [/home/me/chisel-tutorial/generated/examples/Parity.out] Error 127
There is another question regarding the same problem here, where the suggestion to add SHELL=/bin/bash to the Makefile is made. That did not work for me. Another suggestion is to remove set -e -o pipefail: this suggestion actually works but is it OK to remove that option? what does it do?
Edit_1:
I have installed sbt and added its path to the PATH variable.
$ which sbt
/usr/bin/sbt
But still I am getting this error when running make Parity.out
set -e -o pipefail; "sbt" -Dsbt.log.noformat=true -DchiselVersion="2.+" "run Parity --genHarness --compile --test --backend c --vcd --targetDir /home/me/chisel-tutorial/generated/examples " | tee /home/me/chisel-tutorial/generated/examples/Parity.out
/bin/sh: 1: set: Illegal option -o pipefail
make: *** [/home/me/chisel-tutorial/generated/examples/Parity.out] Error 2
If I edit this part of the file suffix.mk:
$(objdir)/%.dot: %.scala
set -e -o pipefail; "$(SBT)" $(SBT_FLAGS) "run $(notdir $(basename $<)) --backend dot --targetDir $(objdir) $(CHISEL_FLAGS)"
$(objdir)/%.out: %.scala
set -e -o pipefail; "$(SBT)" $(SBT_FLAGS) "run $(notdir $(basename $<)) --genHarness --compile --test --backend c --vcd --targetDir $(objdir) $(CHISEL_FLAGS)" | tee $#
By deleting the -o option in the set -e -o pipefail it works, I get the PASSED and [success] message after running $ make Parity.out. So what is going on?
Edit_2:
It is working fine now after I added the SHELL=/bin/bash to the Makefile, so it was first a problem of not having sbt as Nathaniel pointed out then editing the Makefile to include SHELL=/bin/bash.
set -e -o pipefail is a way of making sure that the execution of the bash script both works as expected and that if there is a failure, it halts immediately (rather than at some later stage). Removing it might work - but if there is a failure it might get swallowed and hide the fact it's broken.
But I think your problem lies here, making the other question a bit of a red herring:
/bin/bash: sbt: command not found
Do you have sbt installed on your system? Run which sbt as the user that executes the script. For instance, on my system:
$ which sbt
/opt/local/bin/sbt
If you don't have it on your system, nothing will be returned by running which.
The script clearly needs access to sbt and is failing when it doesn't find it. If you do have it on your system, then there is a mismatch between the user running the script and access to that file. You'll need to post more information about how you're executing the script: in that case it is likely you'll have to update your PATH variables to be able to find the sbt executable.
Given that, after fixing this, you still have a problem, you have to ensure that you're running in bash, and not another terminal type. The reason for this is that bash supports set -o pipefail but a lot of other terminals don't. We suspect this might be the case because of the error messages:
/bin/sh: 1: set: Illegal option -o pipefail
Here we see that /bin/sh (the shell) is being invoked by the program. Use ls -l /bin/sh to determine if your /bin/sh is pointing to a particular shell. If it is not pointed to a bash shell, then you either need to repoint it (be careful! this is probably another question in it's own right), or need to specify to your Scala program to use a specific shell.

equivalent of pipefail in GNU make?

Say I have the following files:
buggy_program:
#!/bin/sh
echo "wops, some bug made me exit with failure"
exit 1
Makefile:
file.gz:
buggy_program | gzip -9 -c >$#
Now if I type make, GNU make will happily build file.gz even though buggy_program exited with non-zero status.
In bash I could do set -o pipefail to make a pipeline exit with failure if at least one program in the pipeline exits with failure. Is there a similar method in GNU make? Or some workaround that doesn't involve temporary files? (The reason for gzipping here is precisely to avoid a huge temporary file.)
Try this
SHELL=/bin/bash -o pipefail
file.gz:
buggy_program | gzip -9 -c >$#
You could do:
SHELL=/bin/bash
.DELETE_ON_ERROR:
file.gz:
set -o pipefail; buggy_program | gzip -9 -c >$#
but this only work with bash.
Here's a possible solution that doesn't require bash. Imagine you have two programs thisworks and thisfails that fail or work fine, respectively. Then the following will only leave you with work.gz, deleting fail.gz, ie. create the gzipped make target if and only if the program executed correctly:
all: fail.gz work.gz
work.gz:
( thisworks && touch $#.ok ) | gzip -c -9 >$#
rm $#.ok || rm $#
fail.gz:
( thisfails && touch $#.ok ) | gzip -c -9 >$#
rm $#.ok || rm $#
Explanation:
In the first line of the work.gz rule, thisworks will exit with success, and a file work.gz.ok will be created, and all stdout goes through gzip into work.gz. Then in the second line, because work.gz.ok exists, the first rm command also exits with success – and since || is short-circuiting, the second rm does not get run and so work.gz is not deleted.
OTOH, in the first line of the fail.gz rule, thisfails will exit with failure, and fail.gz.ok will not be created. All stdout still goes through gzip into fail.gz. Then in the second line, because fail.gz.ok does not exist, the first rm command exits with failure, so || tries the second rm command which deletes the fail.gz file.
To easily check that this works as it should, simply replace thisworks and thisfails with the commands true and false, respectively, put it in a Makefile and type make.
(Thanks to the kind people in #autotools for helping me with this.)

How do I get the exit status from the first command in a pipe within make?

I'm trying to pipe to the output of the compiler to the tee command in windows, but I've ran into an issue where if the compiler fails within make it'll continue compiling the next file when I want to it to stop. Is there a way to have the exit status of the first command be the exit status of the second command?
$(ODIR)/%.o: %.c $(DEPS)
$(CC) -c -o $# $< $(CFLAGS) 2>&1 | tee build_log.txt
First off, I would leave any logging to the caller of make. Second off, this sort of piping is untidy in make. Third off, not a fan of losing the stderr stream inside make.
That said, this is a shell question. If you are using bash then see pipefail in the manual. Unfortunately I think it's quite tricky to turn on. (Yeah, I know you said windows, but I assume you aren't using the execrable cmd.)
SHELL := /bin/bash
passes:
(exit 1) |& cat
fails:
bash -c 'set -o pipefail; (exit 1) |& cat'
After Struggling a lot, I came to this solution ...
.ONESHELL:
$(ODIR)/%.o: %.c $(DEPS)
$(CC) -c -o $# $< $(CFLAGS) 2> temp_err_file
set EXIT_STATUS=%ERRORLEVEL%
type temp_err_file >> build_log.txt
type temp_err_file 1>&2
del /q temp_err_file
exit /b %EXIT_STATUS%
Here .ONESHELL allows make to run entire recipe in the single shell command instead of running each line in separate cmd and collect return status of each separately. Overall exit status depends on the main compilation command so in end it is necessary to exit with the status of compilation.
I know its not a clean solution involving the temp_err_file and if something goes wrong after compilation command, make would not be able to catch it but this I think is the best I can find to work with windows without losing stderr stream and logging.
A method that is shell independent and may be feasible in some cases is the following:
assume you have a recipe:
target:
try_making_target |& tee target.log
What I did was convert it to:
target:
(try_making_target || rm -f $#) |& tee target.log
test -e $#
The the piped command fails, the "fallback" (command after ||) will delete the goal file, and the final test will fail. Note that this example assumes the OS is Linux ('rm' for deletions) and that your shell supports the || operator.
This assumes that you are not interested in partial results when try_making_target fails. If you want to keep partial result in 'target', you can use other "marker" files to designate the success or failure of try_making_target. Something like this may work:
target:
touch $#.succeeded # Assume success
(try_making_target || rm -f $#.succeeded) |& tee target.log # Delete to mark failure
test -e $#.succeeded # Fail if marker
rm $#.succeeded # Remove unneeded marker
This last code would also work for phony goals, although you really should minimize the use of these (I try only using them as mnemonics for real files, which may have longish names).

Resources