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

89,947

Solution 1

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

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

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

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

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
Share:
89,947
jonner
Author by

jonner

C++ hacker

Updated on July 08, 2022

Comments

  • jonner
    jonner almost 2 years

    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?

  • jonner
    jonner about 15 years
    This is true, but it doesn't give me any formatting (newlines). It just becomes a single line of text
  • Roalt
    Roalt about 15 years
    See my answer on why it does not work (at least from my attempt)
  • Roalt
    Roalt about 15 years
    I prefer the answer of Erik Melski but this might do the trick already for you, depending on your application.
  • Shahbaz
    Shahbaz over 12 years
    I got a question on this. This works principally fine, except I see an extra "space" in the beginning of every line (except the first). Does this happen to you to? I can put all the text in one line, separated by \n so effectively creating the output I like. The problem is it looks very ugly in the Makefile itself!
  • Shahbaz
    Shahbaz over 12 years
    I found a workaround. I put the text through $(subst \n ,\n,$(TEXT)) although I wish there was a better way!
  • mbauman
    mbauman about 12 years
    Multi-line here-documents do not work as described in GNU Make.
  • Ding-Yi Chen
    Ding-Yi Chen about 12 years
    The manual does show you the proper way that works: Use echo.
  • MadScientist
    MadScientist about 12 years
    Multiline here docs inside recipes won't work in ANY standard version of make that supports the POSIX standard: the make standard requires that each separate line of the recipe must be run in a separate shell. Make does not do any parsing on the command to tell that it's a here-document or not, and handle it differently. If you know of some variant of make that does support this (I've never heard of one) you should probably state it explicitly.
  • MadScientist
    MadScientist about 12 years
    Note that "echo -e" is not portable. You should probably prefer printf(1) instead.
  • anatoly techtonik
    anatoly techtonik almost 11 years
    export ANNOUNCE_BODY only sets the variable inside rules - it doesn't allow referencing $$ANNOUNCE_BODY to define other variables.
  • Eric Melski
    Eric Melski almost 11 years
    @techtonik if you want to use the value of ANNOUNCE_BODY in other variable definitions, just reference it like any other make variable. For example, OTHER=The variable ANNOUNCE_BODY is $(ANNOUNCE_BODY). Of course you'll still need the export trick if you want to get OTHER out in a command.
  • blueyed
    blueyed about 10 years
    Unfortunately that does not work: make version printf "Version 1.2.3 of foo-bar has been released. /bin/sh: 1: Syntax error: Unterminated quoted string make: *** [version] Error 2 (GNU make 3,81)
  • sphakka
    sphakka about 10 years
    @blueyed, I just tested it with GNU Make 3.82 and GNU bash 4.2.45(1)-release: it works as expected. Also, please check the presence of the leading TAB character, instead of blanks, in front of the @printf ... statement -- it looks like TABs are always rendered as 4 spaces...
  • blueyed
    blueyed about 10 years
    It appears that .ONESHELL is new in make 3.82.
  • blueyed
    blueyed about 10 years
    btw: the error when using spaces instead of a tab would be *** missing separator. Stop..
  • mschilli
    mschilli over 9 years
    great answer, however, I had to remove the = after define ANNOUNCE_BODY to get it running.
  • Guillaume Papin
    Guillaume Papin almost 8 years
    This no-op rule produced an unwanted message: make: 'do-echo' is up to date.. By using a "no op" comand I was able to silence it: @: $(info $(YOUR_MULTILINE_VAR))
  • jlettvin
    jlettvin almost 7 years
    I like this one 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
  • jlettvin
    jlettvin almost 7 years
    Comments do not allow code. Will send as an answer. I like this one 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`
  • Admin
    Admin almost 7 years
    A program's synopsis should be easy and obvious to locate. I'd recommend adding this level of information in a readme and/or manpage. When a user runs make, they generally do so expecting to start a build process.
  • Admin
    Admin almost 7 years
    @jlettvin See my response to your answer. A program's synopsis should definitely not be embedded inside a Makefile, especially not as a default task.
  • Xennex81
    Xennex81 almost 7 years
    I have wanted many times to just see a list of make targets. Your comment makes no sense. What users expect is irrelevant if it takes them 3 seconds to know what to do, whereas in lieu of any information like this, it can sometimes take hours.
  • Xennex81
    Xennex81 almost 7 years
    Using expectations as a reason to do something is also a circular argument: because people expect it, we must do it, and because we do it, they expect it.
  • M3D
    M3D over 6 years
    @GuillaumePapin A bit late, but you can use .PHONY to tell your Makefile that there is nothing to check for that rule. Makefiles were originally for compilers, if I am not mistaken, so make is doing some magic that I don't understand to anticipate that the rule will not change anything, and as such assumes it to be 'done'. Adding .PHONY do-echo in your file will tell make to ignore this and run the code anyway.
  • Daniel Stevens
    Daniel Stevens over 4 years
    You can place $(info ...) outside of a make rule. It will still generate output.
  • Daniel Stevens
    Daniel Stevens over 4 years
    Documentation: Make Control Functions
  • Ярослав Рахматуллин
    Ярослав Рахматуллин over 2 years
    Do you know why it's ok to double-define a target like that?
  • Ivan Ustûžanin
    Ivan Ustûžanin over 2 years
    @ЯрославРахматуллин that's not a double-define, the first line is the target-specific variable definition, and only the second line is the target definition.