Is it possible to create a multi-line string variable in a Makefile

VariablesMakefile

Variables Problem Overview


I want to create a makefile variable that is a multi-line string (e.g. the body of an email release announcement). something like

ANNOUNCE_BODY="
Version $(VERSION) of $(PACKAGE_NAME) has been released

It can be downloaded from $(DOWNLOAD_URL)

etc, etc"

But I can't seem to find a way to do this. Is it possible?

Variables Solutions


Solution 1 - Variables

Yes, you can use the define keyword to declare a multi-line variable, like this:

define ANNOUNCE_BODY
Version $(VERSION) of $(PACKAGE_NAME) has been released.

It can be downloaded from $(DOWNLOAD_URL).

etc, etc.
endef

The tricky part is getting your multi-line variable back out of the makefile. If you just do the obvious thing of using "echo $(ANNOUNCE_BODY)", you'll see the result that others have posted here -- the shell tries to handle the second and subsequent lines of the variable as commands themselves.

However, you can export the variable value as-is to the shell as an environment variable, and then reference it from the shell as an environment variable (NOT a make variable). For example:

export ANNOUNCE_BODY
all:
    @echo "$$ANNOUNCE_BODY"

Note the use of $$ANNOUNCE_BODY, indicating a shell environment variable reference, rather than $(ANNOUNCE_BODY), which would be a regular make variable reference. Also be sure to use quotes around your variable reference, to make sure that the newlines aren't interpreted by the shell itself.

Of course, this particular trick may be platform and shell sensitive. I tested it on Ubuntu Linux with GNU bash 3.2.13; YMMV.

Solution 2 - Variables

Another approach to 'getting your multi-line variable back out of the makefile' (noted by Eric Melski as 'the tricky part'), is to plan to use the subst function to replace the newlines introduced with define in your multi-line string with \n. Then use -e with echo to interpret them. You may need to set the .SHELL=bash to get an echo that does this.

An advantage of this approach is that you also put other such escape characters into your text and have them respected.

This sort of synthesizes all the approaches mentioned so far...

You wind up with:

define newline


endef

define ANNOUNCE_BODY
As of $(shell date), version $(VERSION) of $(PACKAGE_NAME) has been released.  

It can be downloaded from $(DOWNLOAD_URL).  

endef

someTarget:
	echo -e '$(subst $(newline),\n,${ANNOUNCE_BODY})'

Note the single quotes on the final echo are crucial.

Solution 3 - Variables

Assuming you only want to print the content of your variable on standard output, there is another solution :

do-echo:
    $(info $(YOUR_MULTILINE_VAR))

Solution 4 - Variables

Not completely related to the OP, but hopefully this will help someone in future. (as this question is the one that comes up most in google searches).

In my Makefile, I wanted to pass the contents of a file, to a docker build command, after much consternation, I decided to:

 base64 encode the contents in the Makefile (so that I could have a single line and pass them as a docker build arg...)
 base64 decode the contents in the Dockerfile (and write them to a file)

see example below.

nb: In my particular case, I wanted to pass an ssh key, during the image build, using the example from https://vsupalov.com/build-docker-image-clone-private-repo-ssh-key/ (using a multi stage docker build to clone a git repo, then drop the ssh key from the final image in the 2nd stage of the build)

Makefile

...
MY_VAR_ENCODED=$(shell cat /path/to/my/file | base64)

my-build:
	@docker build \
	  --build-arg MY_VAR_ENCODED="$(MY_VAR_ENCODED)" \
  	  --no-cache \
  	  -t my-docker:build .
...

Dockerfile

...
ARG MY_VAR_ENCODED

RUN mkdir /root/.ssh/  && \
    echo "${MY_VAR_ENCODED}" | base64 -d >  /path/to/my/file/in/container
... 

Solution 5 - Variables

Yes. You escape the newlines with \:

VARIABLE="\
THIS IS A VERY LONG\
TEXT STRING IN A MAKE VARIABLE"

###update

Ah, you want the newlines? Then no, I don't think there's any way in vanilla Make. However, you can always use a here-document in the command part

[This does not work, see comment from MadScientist]

foo:
    echo <<EOF
    Here is a multiple line text
    with embedded newlines.
    EOF

Solution 6 - Variables

Why don't you make use of the \n character in your string to define the end-of-line? Also add the extra backslash to add it over multiple lines.

ANNOUNCE_BODY=" \n\
Version $(VERSION) of $(PACKAGE_NAME) has been released \n\
\n\
It can be downloaded from $(DOWNLOAD_URL) \n\
\n\
etc, etc"

Solution 7 - Variables

Just a postscript to Eric Melski's answer: You can include the output of commands in the text, but you must use the Makefile syntax "$(shell foo)" rather than the shell syntax "$(foo)". For example:

define ANNOUNCE_BODY  
As of $(shell date), version $(VERSION) of $(PACKAGE_NAME) has been released.  
  
It can be downloaded from $(DOWNLOAD_URL).  
  
endef

Solution 8 - Variables

You should use "define/endef" Make construct:

define ANNOUNCE_BODY
Version $(VERSION) of $(PACKAGE_NAME) has been released.

It can be downloaded from $(DOWNLOAD_URL).

etc, etc.
endef

Then you should pass value of this variable to shell command. But, if you do this using Make variable substitution, it will cause command to split into multiple:

ANNOUNCE.txt:
  echo $(ANNOUNCE_BODY) > $@               # doesn't work

Qouting won't help either.

The best way to pass value is to pass it via environment variable:

ANNOUNCE.txt: export ANNOUNCE_BODY:=$(ANNOUNCE_BODY)
ANNOUNCE.txt:
  echo "$${ANNOUNCE_BODY}" > $@

Notice:

  1. Variable is exported for this particular target, so that you can reuse that environment will not get polluted much;
  2. Use environment variable (double qoutes and curly brackets around variable name);
  3. Use of quotes around variable. Without them newlines will be lost and all text will appear on one line.

Solution 9 - Variables

With GNU Make 3.82 and above, the .ONESHELL option is your friend when it comes to multiline shell snippets. Putting together hints from other answers, I get:

VERSION = 1.2.3
PACKAGE_NAME = foo-bar
DOWNLOAD_URL = $(PACKAGE_NAME).somewhere.net

define nwln

endef

define ANNOUNCE_BODY
Version $(VERSION) of $(PACKAGE_NAME) has been released.

It can be downloaded from $(DOWNLOAD_URL).

etc, etc.
endef

.ONESHELL:

# mind the *leading* <tab> character
version:
	@printf "$(subst $(nwln),\n,$(ANNOUNCE_BODY))"

Make sure, when copying and pasting the above example into your editor, that any <tab> characters are preserved, else the version target will break!

Note that .ONESHELL will cause all targets in the Makefile to use a single shell for all their commands.

Solution 10 - Variables

This doesn't give a here document, but it does display a multi-line message in a way that's suitable for pipes.

=====

MSG = this is a\\n\
multi-line\\n\
message

method1:
     @$(SHELL) -c "echo '$(MSG)'" | sed -e 's/^ //'

=====

You can also use Gnu's callable macros:

=====

MSG = this is a\\n\
multi-line\\n\
message

method1:
        @echo "Method 1:"
        @$(SHELL) -c "echo '$(MSG)'" | sed -e 's/^ //'
        @echo "---"

SHOW = $(SHELL) -c "echo '$1'" | sed -e 's/^ //'

method2:
        @echo "Method 2:"
        @$(call SHOW,$(MSG))
        @echo "---"

=====

Here's the output:

=====

$ make method1 method2
Method 1:
this is a
multi-line
message
---
Method 2:
this is a
multi-line
message
---
$

=====

Solution 11 - Variables

Solution 12 - Variables

In the spirit of .ONESHELL, it's possible to get pretty close in .ONESHELL challenged environments:

define _oneshell_newline_


endef

define oneshell
@eval "$$(printf '%s\n' '$(strip                            \
                         $(subst $(_oneshell_newline_),\n,  \
                         $(subst \,\/,                      \
                         $(subst /,//,                      \
                         $(subst ','"'"',$(1))))))' |       \
          sed -e 's,\\n,\n,g' -e 's,\\/,\\,g' -e 's,//,/,g')"
endef

An example of use would be something like this:

define TEST
printf '>\n%s\n' "Hello
World\n/$$$$/"
endef

all:
        $(call oneshell,$(TEST))

That shows the output (assuming pid 27801):

>
Hello
World\n/27801/

This approach does allow for some extra functionality:

define oneshell
@eval "set -eux ; $$(printf '%s\n' '$(strip                            \
                                    $(subst $(_oneshell_newline_),\n,  \
                                    $(subst \,\/,                      \
                                    $(subst /,//,                      \
                                    $(subst ','"'"',$(1))))))' |       \
                     sed -e 's,\\n,\n,g' -e 's,\\/,\\,g' -e 's,//,/,g')"
endef

These shell options will:

  • Print each command as it is executed
  • Exit on the first failed command
  • Treat use of undefined shell variables as an error

Other interesting possibilities will likely suggest themselves.

Solution 13 - Variables

I like alhadis's answer best. But to keep columnar formatting, add one more thing.

SYNOPSIS := :: Synopsis: Makefile\
| ::\
| :: Usage:\
| ::    make .......... : generates this message\
| ::    make synopsis . : generates this message\
| ::    make clean .... : eliminate unwanted intermediates and targets\
| ::    make all ...... : compile entire system from ground-up\
endef

Outputs:

:: Synopsis: Makefile 
:: 
:: Usage: 
:: make .......... : generates this message 
:: make synopsis . : generates this message 
:: make clean .... : eliminate unwanted intermediates and targets 
:: make all ...... : compile entire system from ground-up

Solution 14 - Variables

Not really a helpful answer, but just to indicate that 'define' does not work as answered by Ax (did not fit in a comment):

VERSION=4.3.1
PACKAGE_NAME=foobar
DOWNLOAD_URL=www.foobar.com

define ANNOUNCE_BODY
	Version $(VERSION) of $(PACKAGE_NAME) has been released
	It can be downloaded from $(DOWNLOAD_URL)
	etc, etc
endef

all:
	@echo $(ANNOUNCE_BODY)

It gives an error that the command 'It' cannot be found, so it tries to interpret the second line of ANNOUNCE BODY as a command.

Solution 15 - Variables

It worked for me:

ANNOUNCE_BODY="first line\\nsecond line"

all:
    @echo -e $(ANNOUNCE_BODY)

Solution 16 - Variables

GNU Makefile can do things like the following. It is ugly, and I won't say you should do it, but I do in certain situations.

PROFILE = \
\#!/bin/sh.exe\n\
\#\n\
\# A MinGW equivalent for .bash_profile on Linux.  In MinGW/MSYS, the file\n\
\# is actually named .profile, not .bash_profile.\n\
\#\n\
\# Get the aliases and functions\n\
\#\n\
if [ -f \$${HOME}/.bashrc ]\n\
then\n\
  . \$${HOME}/.bashrc\n\
fi\n\
\n\
export CVS_RSH="ssh"\n  
#
.profile:
        echo -e "$(PROFILE)" | sed -e 's/^[ ]//' >.profile

make .profile creates a .profile file if one does not exist.

This solution was used where the application will only use GNU Makefile in a POSIX shell environment. The project is not an open source project where platform compatibility is an issue.

The goal was to create a Makefile that facilitates both setup and use of a particular kind of workspace. The Makefile brings along with it various simple resources without requiring things like another special archive, etc. It is, in a sense, a shell archive. A procedure can then say things like drop this Makefile in the folder to work in. Set up your workspace enter make workspace, then to do blah, enter make blah, etc.

What can get tricky is figuring out what to shell quote. The above does the job and is close to the idea of specifying a here document in the Makefile. Whether it is a good idea for general use is a whole other issue.

Solution 17 - Variables

I believe the safest answer for cross-platform use would be to use one echo per line:

  ANNOUNCE.txt:
    rm -f $@
    echo "Version $(VERSION) of $(PACKAGE_NAME) has been released" > $@
    echo "" >> $@
    echo "It can be downloaded from $(DOWNLOAD_URL)" >> $@
    echo >> $@
    echo etc, etc" >> $@

This avoids making any assumptions of on the version of echo available.

Solution 18 - Variables

Use string substitution:

VERSION := 1.1.1
PACKAGE_NAME := Foo Bar
DOWNLOAD_URL := https://go.get/some/thing.tar.gz

ANNOUNCE_BODY := Version $(VERSION) of $(PACKAGE_NAME) has been released. \
	| \
	| It can be downloaded from $(DOWNLOAD_URL) \
	| \
	| etc, etc

Then in your recipe, put

	@echo $(subst | ,$$'\n',$(ANNOUNCE_BODY))

This works because Make is substituting all occurrences of (note the space) and swapping it with a newline character ($$'\n'). You can think of the equivalent shell-script invocations as being something like this:

Before:

$ echo "Version 1.1.1 of Foo Bar has been released. | | It can be downloaded from https://go.get/some/thing.tar.gz | | etc, etc"

After:

$ echo "Version 1.1.1 of Foo Bar has been released.
>
> It can be downloaded from https://go.get/some/thing.tar.gz
> 
> etc, etc"

I'm not sure if $'\n' is available on non-POSIX systems, but if you can gain access to a single newline character (even by reading a string from an external file), the underlying principle is the same.

If you have many messages like this, you can reduce noise by using a macro:

print = $(subst | ,$$'\n',$(1))

Where you'd invoke it like this:

@$(call print,$(ANNOUNCE_BODY))

Hope this helps somebody. =)

Solution 19 - Variables

As an alternative you can use the printf command. This is helpful on OSX or other platforms with less features.

To simply output a multiline message:

all:
        @printf '%s\n' \
            'Version $(VERSION) has been released' \
            '' \
            'You can download from URL $(URL)'

If you are trying to pass the string as an arg to another program, you can do so like this:

all:
        /some/command "`printf '%s\n' 'Version $(VERSION) has been released' '' 'You can download from URL $(URL)'`"

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionjonnerView Question on Stackoverflow
Solution 1 - VariablesEric MelskiView Answer on Stackoverflow
Solution 2 - VariablesmalcookView Answer on Stackoverflow
Solution 3 - VariablessuperwhoopyView Answer on Stackoverflow
Solution 4 - Variablesmlo55View Answer on Stackoverflow
Solution 5 - VariablesCharlie MartinView Answer on Stackoverflow
Solution 6 - VariablesRoaltView Answer on Stackoverflow
Solution 7 - VariablesJim Van ZandtView Answer on Stackoverflow
Solution 8 - VariablesMaxim KulkinView Answer on Stackoverflow
Solution 9 - VariablessphakkaView Answer on Stackoverflow
Solution 10 - VariablesPaul SanderView Answer on Stackoverflow
Solution 11 - Variablesax.View Answer on Stackoverflow
Solution 12 - VariablesEarlView Answer on Stackoverflow
Solution 13 - VariablesjlettvinView Answer on Stackoverflow
Solution 14 - VariablesRoaltView Answer on Stackoverflow
Solution 15 - VariablesfdbView Answer on Stackoverflow
Solution 16 - VariableskbulgrienView Answer on Stackoverflow
Solution 17 - VariablesBen MartinView Answer on Stackoverflow
Solution 18 - Variablesuser458541View Answer on Stackoverflow
Solution 19 - VariableszeroimplView Answer on Stackoverflow