OK, someone posted a question to SO and that led me to an hour or more of banging my head against a wall trying to understand what's happening... and I can't.
The problem is that the user would like to invoke $(shell ...) and capture errors, even errors that the program being run doesn't exist. The shell function only captures stdout, not stderr. This simple idea won't work of course: out := $(shell bad-program 2>&1) $(info out = $(out)) $ make /bin/sh: bad-program: not found out = because this redirects the output of the bad-program, but it's the shell printing the error not the program. So I suggested this: out := $(shell $(SHELL) -c 'bad-program' 2>&1) $(info out = $(out)) This SHOULD work: the outer shell is invoking a sub-shell with the sub- shell's stderr redirected to its stdout, then the subshell will print the not found error to its stderr (redirected to stdout). But it DOES NOT WORK! $ make /bin/sh: bad-program: not found out = What is happening here?!?! I wrote a little C program to verify my thinking: /* ----- */ #include <stdio.h> #include <unistd.h> #include <sys/wait.h> int main(int argc, char** argv) { char *args[4] = {"/bin/sh", "-c", "/bin/sh -c bad-program 2>&1", NULL}; int pid = fork(); if (pid == 0) { execv(args[0], args); } else { int st; waitpid(pid, &st, 0); } return 0; } /* ----- */ But, this works as expected. Something make is doing is causing problems here. I tried building both with and without posix_spawn and both behave the same so it's nothing like that. If you force the sub-shell to create its own subshell, then it works! This works: out := $(shell $(SHELL) -c 'bad-program | cat' 2>&1) $(info out = $(out)) $ make out = /bin/sh: bad-program: not found