makefile custom functions - cross-platform

I'm trying to make a custom function in a Makefile to detect the current platform and return the proper file accordingly. Here is my attempt.
UNAME := $(shell uname -s)
define platform
ifeq ($(UNAME),Linux)
$1
else ifneq ($(findstring MINGW32_NT, $(UNAME)),)
$2
else ifeq ($(UNAME),Darwin)
$3
endif
endef
all:
#echo $(call platform,linux,windows,mac)
It fails with the following error.
/bin/sh: Syntax error: "(" unexpected
[Finished]make: *** [all] Error 2
What am I doing wrong?

ifeq ... else ... endif are conditional directives in GNU Make, they can't appear inside define ... endef because the latter treats them as a literal text. (Try to remove # sign near echo command and you will see the actual result of evaluating platform function)
I'd move conditionals out of the define directive. Anyway, the target platform can't change during the execution of Make, so there is no need to parse $(UNAME) each time you call platform.
ifeq ($(UNAME),Linux)
platform = $1
else ifneq ($(findstring MINGW32_NT, $(UNAME)),)
platform = $2
else ifeq ($(UNAME),Darwin)
platform = $3
endif

Another option would be to concatenate outputs of uname to form a platform string in a certain format and have platform specific makefiles named accordingly:
ARCH := $(firstword $(shell uname -m))
SYS := $(firstword $(shell uname -s))
# ${SYS}.${ARCH} expands to Linux.x86_64, Linux.i686, SunOS.sun4u, etc..
include ${SYS}.${ARCH}.mk

Related

Running awk from inside CMake execute_process

I am trying to run awk inside CMake execute_process and I am somehow failing to get the expected result. I think I must be running afoul of some escaping rules, but I am not sure what else I need to escape here. Let me demonstrate by way of an MWE.
Consider the following version.h (header guard left out for brevity):
#define LIB_MAJVER 1
#define LIB_MINVER 2
#define LIB_PATCH 3
#define LIB_BUILD 4
Now consider the following straightforward awk command:
awk '$1 ~ /^#define/ && $2 ~ /^LIB_(MAJVER|MINVER|PATCH|BUILD)$/ { print "set(${PRJNAME}_" $2 " " $3 ")" }' version.h
The output is expected to look like this (and does when executed from the shell):
set(${PRJNAME}_LIB_MAJVER 2)
set(${PRJNAME}_LIB_MINVER 9)
set(${PRJNAME}_LIB_PATCH 9)
set(${PRJNAME}_LIB_BUILD 1)
... which is what I intend to include() right after generating it from the version header.
Now consider the following CMakeLists.txt:
cmake_minimum_required(VERSION 3.1)
set(PRJNAME foobar)
project (${PRJNAME})
find_program(AWK awk mawk gawk)
if(AWK MATCHES ".+-NOTFOUND")
message(FATAL_ERROR "FATAL: awk (and mawk and gawk) could not be found (${AWK}).")
else()
execute_process(
COMMAND "${AWK}" '$1 ~ /^#define/ && $2 ~ /^LIB_(MAJVER|MINVER|PATCH|BUILD)$/\ { print "set(${PRJNAME}_" $2 " " $3 ")" }' "${CMAKE_SOURCE_DIR}/version.h"
RESULT_VARIABLE AWK_EXITCODE
OUTPUT_FILE "${CMAKE_CURRENT_BINARY_DIR}/version.cmake"
)
message(STATUS "Exit code from awk: ${AWK_EXITCODE}")
include(${CMAKE_CURRENT_BINARY_DIR}/version.cmake)
endif()
The output of execute_process is:
awk: cmd. line:1: '$1
awk: cmd. line:1: ^ invalid char ''' in expression
... which suggests that what I am trying to execute gets passed verbatim rather than arguments being stripped of their outer quotes. So I didn't even bother to escape the occurrences of $ in the awk command, which I expect have to be escaped.
Now one way to go about this would be to dump this into a shell script as a file (and during development verify that it works with the CMake versions we use). Another should be to use /bin/sh -c, but that raises the question how I can pass that one long command string ...
However, this seems cumbersome and redundant, given there should be a way to achieve what I want.
NB: I am using CMake 3.12.2 on Linux.
I should add that I tried to replace the COMMAND string by the following to test my hypothesis of being able to pull this off with /bin/sh -c.
COMMAND /bin/sh -c [[[ "${AWK}" '$1 ~ /^#define/ && $2 ~ /^LIB_(MAJVER|MINVER|PATCH|BUILD)$/\ { print "set(${PRJNAME}_" $2 " " $3 ")" }' "${CMAKE_SOURCE_DIR}/version.h" ]]]
The result was this error:
CMake Error at CMakeLists.txt:11:
Syntax Error in cmake code at column 180
Argument not separated from preceding token by whitespace.
-- Configuring incomplete, errors occurred!
The comment by Tsyvarev helped me figuring out what was going on (reproduced here, since the actual comment may go away):
Unlike to double quote ("), single quote (') has no special meaning in
CMake. This is why it separates words in '...' argument. Inside double
quoted string, double quotes can be escaped with \".
So I rewrote the COMMAND as follows:
cmake_minimum_required(VERSION 3.1)
set(PRJNAME foobar)
project (${PRJNAME})
find_program(AWK awk mawk gawk)
if(AWK MATCHES ".+-NOTFOUND")
message(FATAL_ERROR "FATAL: awk (and mawk and gawk) could not be found (${AWK}).")
else()
execute_process(
COMMAND /bin/sh -c "\"${AWK}\" '$1 ~ /^#define/ && $2 ~ /^LIB_(MAJVER|MINVER|PATCH|BUILD)$/\ { print \"set(\${PRJNAME}_\" $2 \" \" $3 \")\" }' \"${CMAKE_SOURCE_DIR}/version.h\""
RESULT_VARIABLE AWK_EXITCODE
OUTPUT_FILE "${CMAKE_CURRENT_BINARY_DIR}/version.cmake"
)
message(STATUS "Exit code from awk: ${AWK_EXITCODE}")
include(${CMAKE_CURRENT_BINARY_DIR}/version.cmake)
endif()
The gist is that I am passing the whole awk command to /bin/sh -c, quoted by double quotes and any quotes embedded inside the outer double quotes needed to be escaped, just like the $ character.

Makefile Awk using $ for column gets syntax error, while it works fine on console

I'm using the below line perfectly fine on the console, but when i use it in a makefile i get the below error. I have tried different things for last hour, nothing helps me. Also i have to use '#' before the command in makefile, that is normal only. Any help is greatly welcome.
Command on console
cpio -itv < rootfs.cpio | awk '!/^d/{$8="";print}' | sort -k8 > rootfs.layout.trim
Error
awk: !/^d/{="";print}
awk: ^ syntax error
In makefile
log-rootfs:
# copy the layout dump with all the modification needed to sync with stb output
# get the file list from cpio file => remove the lines with directory name => sort the output and store the same in layout file
cpio -itv < $(ROOTFS_CPIO_FILE) | awk '!/^d/{$8="";print}' | sort -k8 > $(ROOTFS_LAYOUT)
#echo $(ROOTFS_LAYOUT) is created
make is looking for a variable/macro named $8. In general, $ needs to be escaped in the makefile, and if you want to pass a literal $ to the shell, you should use $$ in the makefile. In other words, try:
rule:
... awk '!/^d/{$$8="";print}' ...

Implementing `make check` or `make test`

How can I implement a simple regression test framework with Make? (I’m using GNU Make, if that matters.)
My current makefile looks something like this (edited for simplicity):
OBJS = jscheme.o utility.o model.o read.o eval.o print.o
%.o : %.c jscheme.h
gcc -c -o $# $<
jscheme : $(OBJS)
gcc -o $# $(OBJS)
.PHONY : clean
clean :
-rm -f jscheme $(OBJS)
I’d like to have a set of regression tests, e.g., expr.in testing a “good” expression & unrecognized.in testing a “bad” one, with expr.cmp & unrecognized.cmp being the expected output for each. Manual testing would look like this:
$ jscheme < expr.in > expr.out 2>&1
$ jscheme < unrecognized.in > unrecognized.out 2>&1
$ diff -q expr.out expr.cmp # identical
$ diff -q unrecognized.out unrecognized.cmp
Files unrecognized.out and unrecognized.cmp differ
I thought to add a set of rules to the makefile looking something like this:
TESTS = expr.test unrecognized.test
.PHONY test $(TESTS)
test : $(TESTS)
%.test : jscheme %.in %.cmp
jscheme < [something.in] > [something.out] 2>&1
diff -q [something.out] [something.cmp]
My questions:
• What do I put in the [something] placeholders?
• Is there a way to replace the message from diff with a message saying, “Test expr failed”?
Your original approach, as stated in the question, is best. Each of your tests is in the form of a pair of expected inputs and outputs. Make is quite capable of iterating through these and running the tests; there is no need to use a shell for loop. In fact, by doing this you are losing the opportunity to run your tests in parallel, and are creating extra work for yourself in order to clean up temp files (which are not needed).
Here's a solution (using bc as an example):
SHELL := /bin/bash
all-tests := $(addsuffix .test, $(basename $(wildcard *.test-in)))
.PHONY : test all %.test
BC := /usr/bin/bc
test : $(all-tests)
%.test : %.test-in %.test-cmp $(BC)
#$(BC) <$< 2>&1 | diff -q $(word 2, $?) - >/dev/null || \
(echo "Test $# failed" && exit 1)
all : test
#echo "Success, all tests passed."
The solution directly addresses your original questions:
The placeholders you're looking for are $< and $(word 2, $?) corresponding to the prerequisites %.test-in and %.test-cmp respectively. Contrary to the #reinierpost comment temp files are not needed.
The diff message is hidden and replaced using echo.
The makefile should be invoked with make -k to run all the tests regardless of whether an individual test fails or succeeds.
make -k all will only run if all the tests succeed.
We avoid enumerating each test manually when defining the all-tests variable by leveraging the file naming convention (*.test-in) and the GNU make functions for file names. As a bonus this means the solution scales to tens of thousands of tests out of the box, as the length of variables is unlimited in GNU make. This is better than the shell based solution which will fall over once you hit the operating system command line limit.
Make a test runner script that takes a test name and infers the input filename, output filename and smaple data from that:
#!/bin/sh
set -e
jscheme < $1.in > $1.out 2>&1
diff -q $1.out $1.cmp
Then, in your Makefile:
TESTS := expr unrecognised
.PHONY: test
test:
for test in $(TESTS); do bash test-runner.sh $$test || exit 1; done
You could also try implementing something like automake's simple test framework.
What I ended up with looks like this:
TESTS = whitespace list boolean character \
literal fixnum string symbol quote
.PHONY: clean test
test: $(JSCHEME)
for t in $(TESTS); do \
$(JSCHEME) < test/$$t.ss > test/$$t.out 2>&1; \
diff test/$$t.out test/$$t.cmp > /dev/null || \
echo Test $$t failed >&2; \
done
It’s based on Jack Kelly’s idea, with Jonathan Leffler’s tip included.
I'll address just your question about diff. You can do:
diff file1 file2 > /dev/null || echo Test blah blah failed >&2
although you might want to use cmp instead of diff.
On another note, you might find it helpful to go ahead and take
the plunge and use automake. Your Makefile.am (in its entirety)
will look like:
bin_PROGRAMS = jscheme
jscheme_SOURCES = jscheme.c utility.c model.c read.c eval.c print.c jscheme.h
TESTS = test-script
and you will get a whole lot of really nice targets for free, including a pretty full-featured test framework.

Makefile variable initialization and export

somevar := apple
export somevar
update := $(shell echo "v=$$somevar")
all:
#echo $(update)
I was hoping to apple as output of command, however it's empty, which makes me think export and := variable expansion taking place on different phases. how to overcome this?
The problem is that export exports the variable to the subshells used by the commands; it is not available for expansion in other assignments. So don't try to get it from the environment outside a rule.
somevar := apple
export somevar
update1 := $(shell perl -e 'print "method 1 $$ENV{somevar}\n"')
# Make runs the shell command, the shell does not know somevar, so update1 is "method 1 ".
update2 := perl -e 'print "method 2 $$ENV{somevar}\n"'
# Now update2 is perl -e 'print "method 2 $$ENV{somevar}\n"'
# Lest we forget:
update3 := method 3 $(somevar)
all:
echo $(update1)
$(update2)
echo $(update3)
perl -e 'print "method 4 $$ENV{somevar}\n"'
The output is:
echo method 1
method 1
perl -e 'print "method 2 $ENV{somevar}\n"'
method 2 apple
echo method 3 apple
method 3 apple
perl -e 'print "method 4 $ENV{somevar}\n"'
method 4 apple
#Beta's answer contains the crucial pointer: with GNU make, variables marked with export are only available to [the shells launched for] recipe commands (commands that are part of rules), regrettably not to invocations of $(shell ...) (they only see the environment that make itself saw when it was launched).
There is a workaround, however: explicitly pass the exported variable as an environment variable to the shell function:
update := $(shell somevar='$(somevar)' perl -e 'print "$$ENV{somevar}"')
By prepending the shell command with <var>=<val>, that definition is added as an environment variable to the environment that the command sees - this is a generic shell feature.
Caveat: #Kaz points out in a comment that this method misbehaves if $(somevar) contains certain chars., because the variable expansion is verbatim (no escaping), which can break the resulting shell command, and suggests the following variant to also work with embedded ' instances (breaks the input value into single-quoted substrings with quoted ' spliced in):
update := $(shell somevar='$(subst ','\'',$(somevar))' perl -e 'print "$$ENV{somevar}"')
This should work with all values except multi-line ones (which are rare; there is no workaround for multi-line values that I'm aware of).
On a side note, literal $ chars. in values must be represented as $$, otherwise make will interpret them as references to its own variables.
Note that I've deliberately NOT chosen the OP's original statement, update := $(shell echo "v=$$somevar"), for demonstration, because it contains a pitfall that muddles the issue: due to how the shell evaluates a command line, somevar=apple echo v=$somevar does NOT evaluate to v=apple, because the $somevar reference is expanded before somevar=apple takes effect. To achieve the desired effect in this case, you'd have to use 2 statements: update := $(shell export somevar="$(somevar)"; echo "v=$$somevar")
As for the bug-vs.-feature debate:
While it can be argued that the shell function should see the same environment as recipe commands, the documentation makes no such promise - see http://www.gnu.org/software/make/manual/make.html#Shell-Function. Conversely, http://www.gnu.org/software/make/manual/make.html#Variables_002fRecursion only mentions making exported variables available to recipe commands.
Running the makefile
foo:=apple
export foo
all:
#echo ">"$(shell echo "$$foo")
#echo ">""$$foo"
gives for me (with foo undefined in the environment)
$ make
>
>apple
$ make foo=bar
>
>apple
$ export foo=bar; make
>bar
>apple
$ export foo=bar; make foo=bar
>bar
>bar
Try using the quoted form (update := "v=$$somevar") and let the shell handle expansion when a command is run (you'll still need the export)
Although export does not play nicely with $(shell ...), there is a simple workaround. We can pass the data to the shell script via the command line.
Now of course, environment passage is robust against issues of escaping and quoting. However, the shell language has a single quote quoting method '...' which handles everything. The only problem is that there is no way to get a single quote in there; but of course that is solved by terminating the quote, backslash-escaping the needed single quote and starting a new quote: In other words:
ab'cd -> 'ab'\''cd'
In the shell script executed by $(shell ...) we just generate a variable assignment of the form var='$(...)', where $(...) is some make expression that interpolates suitably escaped material. Thus, Makefile:
somevar := apple with 'quoted' "stuff" and dollar $$signs
shell_escape = $(subst ','\'',$(1))
update := $(shell v='$(call shell_escape,$(somevar))'; echo $$v > file.txt)
.phony: all
all:
cat file.txt
Sample run:
$ make
cat file.txt
apple with 'quoted' "stuff" and dollar $signs
If we want to communicate an environment variable to a command, we can do that using the shell syntax VAR0=val0 VAR1=val1 ... VARn=valn command arg .... This can be illustrated by some minor alterations to the above Makefile:
somevar := apple with 'quoted' "stuff" and dollar $$signs
shell_escape = $(subst ','\'',$(1))
update := $(shell somevar='$(call shell_escape,$(somevar))' env > file.txt)
.phony: all
all:
grep somevar file.txt
Run:
$ make
grep somevar file.txt
somevar=apple with 'quoted' "stuff" and dollar $signs
file.txt contains a dump of environment variables, where we can see somevar. If export in GNU Make did the right thing, we would have been able to just do:
export somevar
update := $(shell env > file.txt)
but the end result is the same.
Since the end result you want is to echo $(update), you would shell_escape anyway, even if GNU Make passed exported vars to $(shell ...). That is to say, look at one more Makefile:
somevar := apple with 'quoted' "stuff" and dollar $$signs
shell_escape = $(subst ','\'',$(1))
update := $(shell v='$(call shell_escape,$(somevar))'; echo $$v)
.phony: all
all:
#echo '$(call shell_escape,$(update))'
#echo $(update)
Output:
apple with 'quoted' "stuff" and dollar $signs
apple with quoted stuff and dollar

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

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?
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.
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.
Assuming you only want to print the content of your variable on standard output, there is another solution :
do-echo:
$(info $(YOUR_MULTILINE_VAR))
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
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
...
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"
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
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:
Variable is exported for this particular target, so that you can reuse that environment will not get polluted much;
Use environment variable (double qoutes and curly brackets around variable name);
Use of quotes around variable. Without them newlines will be lost and all text will appear on one line.
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
---
$
=====
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.
GNU `make' manual, 6.8: Defining Multi-Line 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.
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
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.
It worked for me:
ANNOUNCE_BODY="first line\\nsecond line"
all:
#echo -e $(ANNOUNCE_BODY)
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.
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.
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. =)
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)'`"