Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Makefile recipe with a here-document redirection

Does anyone know how to use a here-document redirection on a recipe?

test:
  sh <<EOF
  echo I Need This
  echo To Work
  ls
  EOF

I can't find any solution trying the usual backslash method (which basically ends with a command in a single line).

Rationale:

I have a set of multi-line recipes that I want to proxy through another command (e.g., sh, docker).

onelinerecipe := echo l1
define twolinerecipe :=
echo l1
echo l2
endef
define threelinerecipe :=
echo l1
echo l2
echo l3
endef

# sh as proxy command and proof of concept
proxy := sh

test1:
  $(proxy) <<EOF
  $(onelinerecipe)
  EOF

test2:
  $(proxy) <<EOF
  $(twolinerecipe)
  EOF

test3:
  $(proxy) <<EOF
  $(threelinerecipe)
  EOF

The solution I would love to avoid: transform multiline macros into single lines.

define threelinerecipe :=
echo l1;
echo l2;
echo l3
endef

test3:
  $(proxy) <<< "$(strip $(threelinerecipe))"

This works (I use gmake 4.0 and bash as make's shell) but it requires changing my recipes and I have a lot. Strip removes the newlines, from the macro, then everything is written in a single line.

My end goal is: proxy := docker run ...

like image 993
Julio Guerra Avatar asked Feb 19 '16 22:02

Julio Guerra


3 Answers

Using the line .ONESHELL: somewhere in your Makefile will send all recipe lines to a single shell invocation, you should find your original Makefile works as expected.

like image 129
user657267 Avatar answered Oct 18 '22 20:10

user657267


When make sees a multi-line block in a recipe (i.e., a block of lines all ending in \, apart from the last), it passes that block un-modifed to the shell. This generally works in bash, apart from here docs.

One way around this is to strip any trailing \s, then pass the resulting string to bash's eval. You do this in make by playing with ${.SHELLFLAGS} and ${SHELL}. You can use both of these in target-specific form if you only want it to kick in for a few targets.

.PHONY: heredoc

heredoc: .SHELLFLAGS = -c eval
heredoc: SHELL = bash -c 'eval "$${@//\\\\/}"'

heredoc:
    @echo First
    @cat <<-there \
        here line1 \
        here anotherline \
    there
    @echo Last

giving

$ make
First
here line1
here anotherline
Last

Careful with that quoting, Eugene. Note the cheat here: I am removing all backslashes, not just the ones at the ends of the line. YMMV.

like image 21
bobbogo Avatar answered Oct 18 '22 19:10

bobbogo


With GNU make, you can combine multi-line variables with the export directive to use a multi-line command without having to turn on .ONESHELL globally:

define script
cat <<'EOF'
here document in multi-line shell snippet
called from the "$@" target
EOF
endef
export script

run:; @ eval "$$script"

will give

here document in multi-line shell snippet
called from the "run" target

You can also combine it with the value function to prevent its value from being expanded by make:

define _script
cat <<EOF
SHELL var expanded by the shell to $SHELL, pid is $$
EOF
endef
export script = $(value _script)

run:; @ eval "$$script"

will give

SHELL var expanded by the shell to /bin/sh, pid is 12712
like image 3
mosvy Avatar answered Oct 18 '22 20:10

mosvy