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

后端 未结 19 1929
借酒劲吻你
借酒劲吻你 2020-11-28 20:27

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 $         


        
相关标签:
19条回答
  • 2020-11-28 20:45

    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.
    0 讨论(0)
  • 2020-11-28 20:46

    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
    
    0 讨论(0)
  • 2020-11-28 20:46

    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. =)

    0 讨论(0)
  • 2020-11-28 20:49

    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.

    0 讨论(0)
  • 2020-11-28 20:52

    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.

    0 讨论(0)
  • 2020-11-28 20:52

    GNU `make' manual, 6.8: Defining Multi-Line Variables

    0 讨论(0)
提交回复
热议问题