Colorise printing of recipe commands - gnu-make

I would like to have the option to run the Makefile with/without a verbose mode and colorise the printing of the commands in the recipe.
After some researching I found that the typical way of achieving a "verbose mode" is by introducing a variable, VERBOSE, that can be set on the command line as shown in the example below.
SHELL=/bin/bash
.PHONY: all hack
red = \033[31;1m
green = \033[32;1m
reset = \033[0m
VERBOSE ?= 0
export VERBOSE
AT_0 := #
AT_1 :=
AT = $(AT_$(VERBOSE))
all:
$(AT) printf '$(green)%s\n$(reset)' "GNU Is Not UNIX"
hack:
#\
if [[ $${VERBOSE} -eq 1 ]]; then \
printf '$(red)%s\n$(reset)' "printf '$(green)%s\n$(reset)' \"GNU Is Not UNIX\""; \
fi; \
printf '$(green)%s\n$(reset)' "GNU Is Not UNIX"
As yo can see one can now optionally display key commands in a recipe:
usr#cmptr $ make
GNU Is Not UNIX
usr#cmptr $ make VERBOSE=1
printf '\033[32;1m%s\n\033[0m' "GNU Is Not UNIX"
GNU Is Not UNIX
Now back to the beginning. Does anyone have a suggestion for how I can modify this approach such that it also colors the command of the recipe without modifying the color of the output of the command itself?
The desired result is displayed int the hack target
usr#cmptr $ make VERBOSE=1 hack
printf '\033[32;1m%s\n\033[0m' "GNU Is Not UNIX"
GNU Is Not UNIX

That's not the best way of handling verbose modes. Take a look at http://make.mad-scientist.net/managing-recipe-echoing/
The output that you're suppressing by adding # at the beginning is printed by make, it's not printed by the shell. There's no way to get make to colorize its output (short of editing the source code for make).
If you want to see the command colorized you'll have to print it out yourself. If you do that, you'll want to use the # literally all the time, and not allow it to be overridden via VERBOSE or whatever. Your rules will all have to have the format:
foo:
# printf '$(green)%s$(reset)\n' 'my command'; my command
If you want verbose mode AS WELL, so that unless you enable it it won't print the command, you have to combine these. One option would be to use a macro you can call, like this:
ifeq ($(VERBOSE),)
run = # $1
else
run = # printf '$(green)%s$(reset)\n' '$(subst ','\'',$1)'; $1
endif
foo:
$(call run,my command)
Note that if my command could contain commas you'll have to hide those from make.

Related

How to set command line args with the space delimited contents of the first command line argument in zsh

I will be getting one command line argument in the script I'm writing which will itself be a space delimited list of the actual command line arguments. I'd like to set the arguments of the current script with these arguments. How might I accomplish that?
I'd like to use set -- but I'm not sure how this would work.
E.g.
Given arguments to my script: -a -b -c
echo $1 # prints "-a -b -c"
You can do this with set -- "${(z)1}". This will split $1 into words, handling quoting the same way the shell itself does:
% cat script.zsh
#!/usr/bin/env zsh
set -- "${(z)1}"
for arg; do
echo "==$arg=="
done
% ./script.zsh "-a -b -c -d'has spaces'"
==-a==
==-b==
==-c==
==-d'has spaces'==
If you also want to remove a level of quotes, use "${(#Q)${(z)1}}" instead.

Unit testing Zsh completion script

I'm trying to write a completion script for Zsh. I'd like to unit test the completion script. For example, I'd like to test that completions for my-command --h include --help.
For Fish, I can use complete -C 'my-command --h', which would then output --help and any other valid completions.
I can't seem to find an equivalent command for Zsh. Does one exist? I've tried things like _main_complete, _complete and _normal, but either they don't support this or I'm not invoking them in the correct way (I get a lot of can only be called from completion function errors).
I get a lot of can only be called from completion function errors
This is because Zsh's completion commands can run only from inside a completion widget, which in turn can only be called while the Zsh Line Editor is active. We can work around this by activating a completion widget on an active command line inside a so-called pseudo terminal:
# Set up your completions as you would normally.
compdef _my-command my-command
_my-command () {
_arguments '--help[display help text]' # Just an example.
}
# Define our test function.
comptest () {
# Gather all matching completions in this array.
# -U discards duplicates.
typeset -aU completions=()
# Override the builtin compadd command.
compadd () {
# Gather all matching completions for this call in $reply.
# Note that this call overwrites the specified array.
# Therefore we cannot use $completions directly.
builtin compadd -O reply "$#"
completions+=("$reply[#]") # Collect them.
builtin compadd "$#" # Run the actual command.
}
# Bind a custom widget to TAB.
bindkey "^I" complete-word
zle -C {,,}complete-word
complete-word () {
# Make the completion system believe we're on a normal
# command line, not in vared.
unset 'compstate[vared]'
_main_complete "$#" # Generate completions.
# Print out our completions.
# Use of ^B and ^C as delimiters here is arbitrary.
# Just use something that won't normally be printed.
print -n $'\C-B'
print -nlr -- "$completions[#]" # Print one per line.
print -n $'\C-C'
exit
}
vared -c tmp
}
zmodload zsh/zpty # Load the pseudo terminal module.
zpty {,}comptest # Create a new pty and run our function in it.
# Simulate a command being typed, ending with TAB to get completions.
zpty -w comptest $'my-command --h\t'
# Read up to the first delimiter. Discard all of this.
zpty -r comptest REPLY $'*\C-B'
zpty -r comptest REPLY $'*\C-C' # Read up to the second delimiter.
# Print out the results.
print -r -- "${REPLY%$'\C-C'}" # Trim off the ^C, just in case.
zpty -d comptest # Delete the pty.
Running the example above will print out:
--help
If you want to test the entire completion output and not just the strings that would be inserted on the command line, then see https://unix.stackexchange.com/questions/668618/how-to-write-automated-tests-for-zsh-completion/668827#668827

Set a variable inside prerequisites or eval function

I have this code, and it works, but as you see I do the substitute three times, I would like to set a variable to the value, but with no success
$($(PKG)-py-valgrind-tests-status): $($(PKG)-swig-dlib)
$($(PKG)-py-valgrind-tests-status): $(OBJ_OUTPUT_DIR)%.valgrind_passed: %.py
#echo env $(PKG-TEST-HELPER-ENV) valgrind $(VALGRIND-FLAGS) --log-file=$(subst valgrind_passed,valgrind.log,$#) $(PYTHON_BIN) $< -v ; \
env $(PKG-TEST-HELPER-ENV) $(VALGRIND) $(VALGRIND-FLAGS) --log-file=$(subst valgrind_passed,valgrind.log,$#) $(PYTHON_BIN) $< -v \
|| (cat $(subst valgrind_passed,valgrind.log,$#); exit 1)
#touch $#
the problematic line $(subst valgrind_passed,valgrind.log,$#)
I tried:
$($(PKG)-py-valgrind-tests-status): LOG-FILE = $(subst valgrind_passed,valgrind.log,$#)
and
.SECONDEXPANSION:
$($(PKG)-py-valgrind-tests-status): LOG-FILE = $$(subst valgrind_passed,valgrind.log,$#)
and(inside the recipe)
$(eval LOG-FILE = $$(subst valgrind_passed,valgrind.log,$#))
but for all, if I write
--log-file=$(LOG-FILE)
log file come's up empty.
I have no more ideas on how to go forward,
appreciate the help, thanks!
Are you sure you're using GNU make? What version are you using (run make --version)?
There's nothing wrong with your first attempt, using a target-specific variable. If this doesn't work then you've got something wrong or different about your makefile that you haven't explained. Maybe if you showed the actual complete section of the makefile with the target-specific variable being set and used we might see what's wrong.
Your second attempt can't work because secondary expansion applies only the prerequisites, not target-specific variables (but, as above, it's not needed anyway).
Your third attempt might work but again, without seeing exactly what you do with the eval we can't say for sure. You don't need to double the $ before the subst function; it can be expanded first and it will still work.

Make zsh complete arguments from a file

zsh is great but its completion system is very diverse. And the documentation lacks good examples. Is there a template for completing for a specific application. The completion would get its match data from a file, separated by newlines?
I tried modifying an older example of mine that takes match data "live":
~ % cat .zsh/completers/_jazzup
#compdef jazz_up
_arguments "2: :(`mpc lsplaylists|sed -e 's# #\\\\ #g'`)"
I could supply cat my_file there instead of mpc invocation and so on but would there be a more elegant way to do this simple task? And that completion there is placement-specific: can you provide an example where zsh would attempt to complete at any point after the program name is recognized?
The match data will have whitespaces and so on, the completion should escape the WS. Example of that:
Foo bar
Barbaric
Get it (42)
Now if that completion would be configured for a command Say, we should get this kind of behaviour out of zsh:
$ Say Fo<TAB>
$ Say Foo\ bar
$ Say Ge<TAB>
$ Say Get\ it\ \(42\)
Simple completion needs are better addressed with _describe, it pairs an array holding completion options and a description for them (you can use multiple array/description pairs, check the manual).
(_arguments is great but too complex.)
[...]
First create a file
echo "foo\nbar\nbaz\nwith spac e s\noh:noes\noh\:yes" >! ~/simple-complete
Then create a file _simple somewhere in your $fpath:
#compdef simple
# you may wish to modify the expansion options here
# PS: 'f' is the flag making one entry per line
cmds=( ${(uf)"$(< ~/simple-complete)"} )
# main advantage here is that it is easy to understand, see alternative below
_describe 'a description of the completion options' cmds
# this is the equivalent _arguments command... too complex for what it does
## _arguments '*:foo:(${cmds})'
then
function simple() { echo $* }
autoload _simple # do not forget BEFORE the next cmd!
compdef _simple simple # binds the completion function to a command
simple [TAB]
it works. Just make sure the completion file _simple is placed somewhere in your fpath.
Notice that : in the option list is supposed to be used for separating an option from their (individual) description (oh:noes). So that won't work with _describe unless you quote it (oh\:yes). The commented out _arguments example will not use the : as a separator.
Without changing anything further in .zshrc (I already have autoload -Uz compinit
compinit) I added the following as /usr/local/share/zsh/site-functions/_drush
#compdef drush
_arguments "1: :($(/usr/local/bin/aliases-drush.php))"
Where /usr/local/bin/aliases-drush.php just prints a list of strings, each string being a potential first argument for the command drush. You could use ($(< filename)) to complete from filename.
I based this on https://unix.stackexchange.com/a/458850/9452 -- it's surprising how simple this is at the end of the day.

How can I tell if a makefile is being run from an interactive shell?

I have a makefile which runs commands that can take a while. I'd like those commands to be chatty if the build is initiated from an interactive shell but quieter if not (specifically, by cron). Something along the lines of (pseudocode):
foo_opts = -a -b -c
if (make was invoked from an interactive shell):
foo_opts += --verbose
all: bar baz
foo $(foo_opts)
This is GNU make. If the specifics of what I'm doing matter, I can edit the question.
It isn't strictly determining whether it is invoked from an interactive shell or not, but for a cron job in which the output is redirected to a file, the answer to this question would be the same as for How to detect if my shell script is running through a pipe?:
if [ -t 0 ]
then
# input is from a terminal
fi
Edit: To use this to set a variable in a Makefile (in GNU make, that is):
INTERACTIVE:=$(shell [ -t 0 ] && echo 1)
ifdef INTERACTIVE
# is a terminal
else
# cron job
endif
http://www.faqs.org/faqs/unix-faq/faq/part5/section-5.html
5.5) How can I tell if I am running an interactive shell?
In the C shell category, look for the variable $prompt.
In the Bourne shell category, you can look for the variable $PS1,
however, it is better to check the variable $-. If $- contains
an 'i', the shell is interactive. Test like so:
case $- in
*i*) # do things for interactive shell
;;
*) # do things for non-interactive shell
;;
esac
I do not think you can easily find out. I suggest adopting an alternative strategy, probably by quelling the verbose output from the cron job. I would look to do that using a makefile like this:
VERBOSE = --verbose
foo_opts = -a -b -c ${VERBOSE}
all: bar baz
foo $(foo_opts)
Then, in the cron job, specify:
make VERBOSE=
This command-line specification of VERBOSE overrides the one in the makefile (and cannot be changed by the makefile). That way, the specialized task (cron job) that you set up once and use many times will be done without the verbose output; the general task of building will be done verbosely (unless you elect to override the verbose-ness on the command line).
One minor advantage of this technique is that it will work with any variant of make; it does not depend on any GNU Make facility.
I’m not really sure what "am interactive" means. Do you mean if you have a valid /dev/tty? If so, then you could check that. Most of us check isatty on stdin, though, because it answers the questions we want to know: is there someone there to type something.
Just a note: you can also see the related discussion that I had about detecting redirection of STDOUT from inside a Makefile.
I believe it will be helpful to readers of this question - executive summary:
-include piped.mk
all: piped.mk
ifeq ($(PIPED),1)
#echo Output of make is piped because PIPED is ${PIPED}
else
#echo Output of make is NOT piped because PIPED is ${PIPED}
endif
#rm -f piped.mk
piped.mk:
#[ -t 1 ] && PIPED=0 || PIPED=1 ; echo "PIPED=$${PIPED}" > piped.mk
$ make
Output of make is NOT piped because PIPED is 0
$ make | more
Output of make is piped because PIPED is 1
In my answer there I explain why the [-t 1] has to be done in an action and not in a variable assignment (as in the recommended answer here), as well as the various pitfalls regarding re-evaluation of a generated Makefile (i.e. the piped.mk above).
The term interactive in this question seems to imply redirection of STDIN... in which case replacing [ -t 1 ] with [ -t 0 ] in my code above should work as-is.
Hope this helps.

Resources