Search code examples
makefile

How to solve "Unexpected end of file" error from Makefile recipe?


I have the following recipe:

ZS_FPATH := $(filter-out %.awk %.py %.sed, $(wildcard src/*))                                                         
compile: ${ZS_FPATH}
  mkdir -p ${PACKAGE_NAME}/bin        
  zsh -c "zcompile -Uz ${PACKAGE_NAME}/bin/${PACKAGE_NAME} ${ZS_FPATH}"

  mkdir -p ${PACKAGE_NAME}/etc                                                                                        
  :> ${PACKAGE_NAME}/etc/zsfnames.lst                                                                                 
  while IFS="" read -r zsfpath || [ -n "${zsfpath}" ]; do                                                             
    basename $${zsfpath} >> ${PACKAGE_NAME}/etc/zsfnames.lst;                                                         
  done < "${ZS_FPATH}"                                                                                                
.PHONY: compile
# ------------------------------------------------------------------------------                                      

When I run the target "compile", the following error is returned:

mkdir -p utk/bin
zsh -c "zcompile -Uz utk/bin/utk src/bckpfile src/echopath src/tievalue"
mkdir -p utk/etc
:> utk/etc/zsfnames.lst
while IFS="" read -r zsfpath || [ -n "" ]; do
/bin/sh: -c: line 2: syntax error: unexpected end of file
make: *** [Makefile:63: compile] Error 2

I'm using tabs for spacing with tabstop=2 in neovim. I've also tried vs code without success. Could someone point out the problem with the snippet?


Solution

  • Every logical line in a makefile recipe is invoked in a separate shell. It's not true that the entire recipe is run in a single shell. So:

    compile: ${ZS_FPATH}
            while IFS="" read -r zsfpath || [ -n "${zsfpath}" ]; do
                basename $${zsfpath} >> ${PACKAGE_NAME}/etc/zsfnames.lst;
            done < "${ZS_FPATH}"
    

    Will be run by make as:

    /bin/sh -c 'while IFS="" read -r zsfpath || [ -n "${zsfpath}" ]; do'
    /bin/sh -c 'basename $${zsfpath} >> ${PACKAGE_NAME}/etc/zsfnames.lst;'
    /bin/sh -c 'done < "${ZS_FPATH}"'
    

    You can probably see why the shell is complaining now.

    If you want to combine multiple physical lines into one logical line you need to add backslashes in your makefile:

    compile: ${ZS_FPATH}
            while IFS="" read -r zsfpath || [ -n "$${zsfpath}" ]; do \
                basename $${zsfpath} >> ${PACKAGE_NAME}/etc/zsfnames.lst; \
            done < "${ZS_FPATH}"
    

    which will work. Note you also need to escape the ${zfspath} else it will be expanded as a shell variable before the recipe is invoked.