5

The Makefile is:

%.pdf: %.tex
    rubber -d $<

If there is a doc.tex in the directory, then make doc.pdf builds doc.pdf. The problem is that when I type make, the autocompletion gives nothing: it doesn't even allow to autocomplete to make doc.tex. What can be done about it?

  • Completion is handled by bash-completion, not by bash, nor by make. bash-completion needs a list of valid targets to show you. I don't think it's possible to convince make to produce such a list when you're using wildcard rules. – Satō Katsura Oct 26 '16 at 7:01
  • 1
    Even zsh's completion isn't that smart. – Gilles Oct 27 '16 at 22:49
  • Possible duplicate: stackoverflow.com/q/516305/21348 – asalamon74 Aug 11 '17 at 13:55
0

The bash-completion package doesn't do this, it does some acrobatics to handle both command line options and extract a list of Makefile targets, but it does not try to generate matches by applying wildcards or otherwise handling any pattern rules.

It can be done however, here's a simple version with a few caveats.

function _mkcache() {
    local _file="$1"
    # add "-r" to omit defaults (60+ rules)
    ${MAKE:-make} ${_file:+-f "$_file"} -qp 2>/dev/null |
    gawk '/^# *Make data base/,/^# *Finished Make data base/{
      if (/^# Not a target/) { getline; next }
      ## handle "target: ..."
      if (match($0,/^([^.#% ][^:%=]+) *:($|[^=])(.*)/,bits)) {
          #if (bits[3]=="") next # OPT: skip phony
          printf("%s\n",bits[1])
      }
      ## handle "%.x [...]: %.y [| x]", split into distinct targets/prereqs
      else if (match($0,/^([^:]*%[^:]*) *(::?) *(.*%.*) *(\| *(.*))?/,bits)) {
          #if (bits[3]=="%") next # OPT: skip wildcard ones
          nb1=split(bits[1],bb1)
          nb3=split(bits[3],bb3)
          for (nn=1; nn<=nb1; nn++) 
            for (mm=1; mm<=nb3; mm++) 
              printf("%s : %s\n",bb1[nn],bb3[mm])
      }
      ## handle fixed (no %) deps
      else if (match($0,/^([^:]*%[^:]*) *(::?) *([^%]*)$/,bits)) {
          if (bits[3]=="") next # phony
          printf("%s : %s\n",bits[1],bits[3])
      }
      ## handle old form ".c.o:"  rewrite to new form "%.o: %.c"
      else if (match($0,/^\.([^.]+)\.([^.]+): *(.*)/,bits)) {
          printf("%%.%s : %%.%s\n", bits[2],bits[1])
      }
    }' > ".${_file:-Makefile}.targets"
}

function _bc_make() {
    local ctok=${COMP_WORDS[COMP_CWORD]}   # curr token
    local ptok=${COMP_WORDS[COMP_CWORD-1]} # prev token
    local -a mkrule maybe
    local try rr lhs rhs rdir pat makefile=Makefile

    ## check we're not doing any make options 
    [[ ${ctok:0:1} != "-" && ! $ptok =~ ^-[fCIjloW] ]] && {
        COMPREPLY=()
        [[ "$makefile" -nt .${makefile}.targets ]] && 
            _mkcache "$makefile"

        mapfile -t mkrule < ".${makefile}.targets"
        # mkrule+=( "%.o : %.c" )  # stuff in extra rules

        for rr in "${mkrule[@]}"; do
            IFS=": " read lhs rhs <<< $rr

            ## special "archive(member):"
            [[ "$lhs" =~ ^(.*)?\((.+)\) ]] && {
                continue # not handled
            }

            ## handle simple targets
            [[ "$rhs" == "" ]] && {
                COMPREPLY+=( $(compgen -W "$lhs" -- "$ctok" ) )
                continue
            }

            ## rules with a path, like "% : RCS/%,v" 
            rdir=""
            [[ "$rhs" == */* ]] && rdir="${rhs/%\/*/}/" 
            rhs=${rhs/#*\//}

            ## expand (glob) that matches RHS 
            ## if current token already ends in a "." strip it
            ## match by replacing "%" stem with "*"

            [[ $ctok == *. ]] && try="${rdir}${rhs/\%./$ctok*}" \
                              || try="${rdir}${rhs/\%/$ctok*}"

            maybe=( $(compgen -G "$try") )  # try must be quoted

            ## maybe[] is an array of filenames from expanded prereq globs
            (( ${#maybe[*]} )) && {

               [[ "$rhs" =~ % ]] && {
                   ## promote rhs glob to a regex: % -> (.*)
                   rhs="${rhs/./\\.}"
                   pat="${rdir}${rhs/\%/(.*)}"

                   ## use regex to extract stem from RHS, sub "%" on LHS
                   for nn in "${maybe[@]}"; do 
                       [[ $nn =~ $pat ]] && {
                           COMPREPLY+=( "${lhs/\%/${BASH_REMATCH[1]}}" )
                       }
                   done
               } || {
                   # fixed prereqs (no % on RHS)
                   COMPREPLY+=( "${lhs/\%/$ctok}" )   
               }
            }
        done
        return
    }
    COMPREPLY=() #default
}
complete -F _bc_make ${MAKE:-make}

There are two parts, a function _mkcache extracts all the rules and targets from a Makefile and caches these. It also does a bit of processing so the rules are simplified to a single "target : pre-req" form in that cache.

Then, a completion function _bc_make takes the token you attempt completion on and tries to match against targets, and uses the pattern rules to expand a glob based on the pre-requisites and the word for completion. If one or more matches are found, it builds a list of targets based on the pattern rules.

GNU make is assumed. It should correctly handle:

  • targets and pattern rules (though not all of them, see below)
  • new and old form .c.o%.o : %.c
  • paths in prereqs (e.g RCS/)
  • with or without all default rules (add -r to make if preferred)

Caveats, and not supported:

  • intermediate or chained dependencies, it's not as smart as make
  • VPATH or vpath
  • .SUFFIXES
  • make -C dir
  • "archive(member)" targets, explicit or implicit
  • make options expansion
  • pathological junk in the environment that can cause Makefile parsing problems (TERMCAP for example)
  • Makefiles named other than Makefile

Somme of the above can be added relatively simply, others like the archive handling are not so simple.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.