AlwaysLearning
AlwaysLearning

Reputation: 8011

Have `make` echo to standard error without re-direction?

Some of the targets in my Makefile run programs whose output (which they send to stdout) I am interested in. For a reason not known to me, the authors of make decided to echo the executed commands to stdout, which pollutes the latter.

A hard way around this problem that involves swapping file descriptors was suggested here. I am wondering if there is a simpler way to force make echo to stderr.

I looked through the man page of make, but did not find anything to this end besides the -s option. I prefer to preserve the echo of commands, but have it in stderr.

I also tried making an auxiliary target (which I made a prerequisite of all other targets), in which I put:

exec 3>&2
exec 2>&1
exec 1>&3

but bash complained that 3 wasn't a valid file descriptor. I tried only exec 1>&2, but that did not have any effect...

Upvotes: 3

Views: 2124

Answers (5)

domson
domson

Reputation: 221

Another posix-incompatible solution is to put

#!/bin/bash
exec 3>&2; exec 2>&1; exec 1>&3;

into helper/stderr relative to my project, and

helperdir = helper
SHELL = BASH_ENV="$(helperdir)/stderr" /bin/bash

into my Makefile.

Now all executed rule code output is redirected to stderr file descriptor.

BASH_ENV environment variable does, if set to a script path, execute that script at every bash invocation.

Upvotes: 0

AlwaysLearning
AlwaysLearning

Reputation: 8011

Suppose we have the following Makefile:

target-1:
    target-1-body
target-2:
    target-2-body
target-3:
    target-3-body

We change it as follows:

target-1-raw:
    target-1-body
target-2-raw:
    target-2-body
target-3-raw:
    target-3-body

%-raw:
    echo "Error: Target $@ does not exist!"    
%:
    @make $@-raw 3>&2 2>&1 1>&3

The invocation is same as before, e.g. make target-1.

With two additional targets we made make output to stderr.

FYI: I am trying to develop this solution further so the user would not be able to invoke the raw targets directly.

Upvotes: 0

Kaz
Kaz

Reputation: 58598

What you can do entirely in the Makefile is this:

define REDIR
@printf 1>&2 "%s\n" '$(1)'; $(1)
endef

.PHONY: all
all:
        $(call REDIR,echo updating .stamp)
        $(call REDIR,touch .stamp)

That is to say, take control of the command echoing yourself via a macro. Unfortunately, it involves writing your recipe lines as `$(call ...) syntax.

REDIR now implements the semantics of echoing the command, and executing it, via macro expansion.

The 1>&2 is Bash-specific syntax for duplicating the standard error file descriptor to standard out, so the command then effectively prints to standard output.

Test run:

$ make
echo updating .stamp
updating .stamp
touch .stamp

$ make 2> /dev/null
updating .stamp

As you can see, updating .stamp, which is the output of our explicitly coded echo line, nicely goes to standard output. The commands are implicitly sent to standard error.

Upvotes: 2

Alex Cohn
Alex Cohn

Reputation: 57163

If you don't want to pollute the output of echo of what make produces, can't you simply run

make -n >&2 && make -s

This is the sample Makefile:

all:
    ls
    echo done

Here is the output of make:

ls
Makefile
echo done
done

Here is output of make -n >&2 && make -s:

ls
echo done
Makefile
done

Naturally, output of either step can be redirected to file.

Upvotes: 1

MadScientist
MadScientist

Reputation: 100876

The reason make shows the command line on stdout is because that's what the POSIX standard for make requires, and 30+ years of history expect. See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/make.html and search for the section on "STDOUT".

You cannot modify the file descriptors in the make program from within a recipe, because the recipe is run in a subshell: any changes you make to the file descriptors only take effect in the subshell. It's not possible in UNIX for a child process to modify the file descriptors of its parent.

Similarly, each line in a recipe in make is run in a different subshell. If you want to do fancy things like redirect output for a recipe you'll have to write it all on one line:

exec 3>&2; exec 2>&1; exec 1>&3; <my command here>

Of course if you intend to do this a lot I would put that in a make variable and use that variable instead.

There is no way to get make to write its output to stderr instead of stdout, unless you want to modify the source code for GNU make and use the version you build yourself instead. It would actually be straightforward to do this as long as you're using a newer version of GNU make (4.0 and above) since all output is generated from one plase (in output.c).

Upvotes: 5

Related Questions