#compdef make gmake pmake dmake local prev="$words[CURRENT-1]" file expl tmp is_gnu dir incl expandVars() { local open close var val tmp=$2 ret=$2 if (( $1 == 0 )); then return fi while :; do var=${tmp#*\$} if [[ $var != $tmp ]]; then tmp=$var case $var in (\(*) open='(' close=')' ;; ({*) open='{' close='}' ;; ([[:alpha:]]*) open='' close='' var=${(s::)var[1]} ;; (\$*) # avoid parsing second $ in $$ tmp=${tmp#\$} ;& (*) continue ;; esac if [[ $open != '' ]]; then var=${var#$open} var=${var%%$close*} fi case $var in ([[:alnum:]_]#) val=${(P)var} val=$(expandVars $(($1 - 1)) $val) ret=${ret//\$$open$var$close/$val} ;; esac else print -- ${ret//\$\$/\$} return fi done } # parseMakefile only runs inside $(...), so it doesn't matter that # it pollutes the global namespace, setting zsh variables to # make variables. The difficult case is where a make variable # is special in zsh; we use local -h to hide those. This # isn't a complete solution since it means variables defined in # included Makefiles are undefined before returning to the parent. parseMakefile() { local input var val TAB=$'\t' dir=$1 while read input; do case "$input " in ([[:alnum:]][[:alnum:]_]#[ $TAB]#=*) var=${input%%[ $TAB]#=*} val=${input#*=} val=${val##[ $TAB]#} [[ ${(tP)var} = *special ]] && local -h $var eval $var=\$val ;; ([[:alnum:]][[:alnum:]_]#[ $TAB]#:=*) var=${input%%[ $TAB]#:=*} val=${input#*=} val=${val##[ $TAB]#} val=$(expandVars 10 $val) [[ ${(tP)var} = *special ]] && local -h $var eval $var=\$val ;; ([[:alnum:]][^$TAB:=]#:[^=]*) input=${input%%:*} print $(expandVars 10 $input) ;; (${~incl} *) local f=${input##${~incl} ##} if [[ $incl = '.include' ]]; then f=${f#[\"<]} f=${f%[\">]} fi f=$(expandVars 10 $f) case $f in (/*) ;; (*) f=$dir/$f ;; esac if [ -r $f ]; then parseMakefile ${f%%/[^/]##} < $f fi ;; esac done } findBasedir () { local file index basedir basedir=$PWD for ((index=0; index<$#@; index++)); do if [[ $@[index] = -C ]]; then file=${~@[index+1]}; if [[ -z $file ]]; then # make returns with an error if an empty arg is given # even if the concatenated path is a valid directory return elif [[ $file = /* ]]; then # Absolute path, replace base directory basedir=$file else # Relative, concatenate path basedir=$basedir/$file fi fi done print -- $basedir } _pick_variant -r is_gnu gnu=GNU unix -v -f if [[ $is_gnu = gnu ]]; then incl="(-|)include" else incl=.include fi if [[ "$prev" = -[CI] ]]; then _files -W ${(q)$(findBasedir ${words[1,CURRENT-1]})} -/ elif [[ "$prev" = -[foW] ]]; then _files -W ${(q)$(findBasedir $words)} else file="$words[(I)-f]" if (( file )); then file=${~words[file+1]} [[ $file = [^/]* ]] && file=${(q)$(findBasedir $words)}/$file [[ -r $file ]] || file= else local basedir basedir=${(q)$(findBasedir $words)} if [[ $is_gnu = gnu && -r $basedir/GNUmakefile ]]; then file=$basedir/GNUmakefile elif [[ -r $basedir/makefile ]]; then file=$basedir/makefile elif [[ -r $basedir/Makefile ]]; then file=$basedir/Makefile else file='' fi fi if [[ -n "$file" ]] && _tags targets; then if [[ $is_gnu = gnu ]] && zstyle -t ":completion:${curcontext}:targets" call-command; then tmp=( $(_call_program targets "$words[1]" -nsp --no-print-directory -f "$file" .PHONY 2> /dev/null | parseMakefile $PWD) ) else tmp=( $(parseMakefile $PWD < $file) ) fi _wanted targets expl 'make target' compadd -a tmp && return 0 fi compstate[parameter]="${PREFIX%%\=*}" compset -P 1 '*=' _value "$@" fi