Does any make variant support dependency filename with colon? - makefile

I saw this question: Escaping colons in filenames in a Makefile, which is explicitly in reference to GNU make. The accepted answer references a discussion on the bug-make#gnu.org mailing list which states pretty definitively that it's impossible; GNU make does not support files with colons or spaces in the name.
Fair enough. But is there any make variant that does? I'm guessing the answer is still no but I wanted to see if there's an answer more definitive than my guess.

You can kind of use colons but it requires extra work. You can use a colon in a target or a prerequisite if you escape it with a backslash... but then it's escaped with a backslash if you use that variable in your recipe, too:
FOO = biz\:baz
all: foo\:bar $(FOO) ; #echo '$#: $^'
foo\:bar: ; #echo '$#'
$(FOO): ; #echo '$#' '$(FOO)'
This gives:
foo:bar
biz:baz biz\:baz
all: foo:bar biz:baz
Note how the expansion of $(FOO) (and only that!) contains the backslash. So, while it's do-able, it's problematic. As you start to work with it you'll run into all kinds of issues.
As for whitespace, you can also escape that in targets and prerequisites with backslashes. However, again the problem is that the backslashes are included sometimes in the recipe and not other times. Whitespace has an even bigger problem, because the functions make uses to manipulate words are not backslash-aware so things like addsuffix, etc. will not work right.
I'm not aware of any general-purpose make variant that handles this better, at the moment.

Related

Escaping # in gmake $(shell) function

I have an environment variable GITHUB_REFS that I want to perform some bashism on and capture the result in another variable GITHUB_BRANCH from a GNU makefile. My naive approach looks like this:
SHELL:=/bin/bash
GITHUB_BRANCH:=$(shell echo "${GITHUB_REF#refs/heads/}")
If I run the bashism by itself, it works fine however when running the makefile above it fails with:
Makefile:2: *** unterminated call to function 'shell': missing ')'. Stop.
I tried escaping the # as \#, since it is a plausible culprit, and indeed then the Makefile works however the bashism does not. Double escaping it gives the same error again.
So how can I pull this off?
You also need to double the dollar sign to pass it through to the shell.
GITHUB_BRANCH:=$(shell echo "$${GITHUB_REF\#refs/heads/}")
For what it's worth, this simple parameter expansion is portable to any reasonably modern sh, so not at all an exclusive Bash feature.
Of course, make is perfectly capable of performing the same substitution, without invoking an external process.
GITHUB_BRANCH := $(patsubst refs/heads/%,%,${GITHUB_REF})
The # can be escaped using \, but you also forgot to escape the $.
This Makefile works:
SHELL:=/bin/bash
foo := $(shell echo "$${SHELL\#/bin/}")
all:
echo $(foo)
Just to note that this (the need to escape the #) is a bug, and will be fixed in an upcoming version of GNU make. If you want to allow your makefile to be portable before/after the bug is fixed, you should hide it in a variable like this:
HASH := \#
foo := $(shell echo "${GITHUB_REF$(HASH)refs/heads/}")
This will work in all versions of GNU make.

can't use variable in Makefile

My main Makefile call config.mk
include $(TOPDIR)/config.mk
then config.mk include some sentences like this:
ifdef CPU
sinclude $(TOPDIR)/cpu/$(CPU)/config.mk
endif
ifdef SOC
sinclude $(TOPDIR)/cpu/$(CPU)/$(SOC)/config.mk
endif
I have prepared these two tree and necessary config.mks. But for "SOC", whose value is "versatile", there is a problem. If I put "versatile" directly here, it could find the file and everything is fine; but when I use $(SOC), il will meet an error, and say
/../../../cpu/arm926ejs/versatile: is a folder, stop
Anyone know what the problem is ??
Are you sure you gave the exact error message? What version of make are you using? That error doesn't look like anything GNU make would print.
Anyway, I'll bet the problem is that your assignment of the SOC variable has trailing whitespace. According to the POSIX definition of make, leading whitespace before a variable value is removed, but trailing whitespace is preserved. That means, for example, if you write your makefile like this:
SOC = versatile # this is the versatile SOC
then make will remove the comment, but keep the space, so the value will be 'versatile' (space at the end). This means when the value is expanded in the sinclude line you get:
sinclude $(TOPDIR)/cpu/$(CPU)/versatile /config.mk
which make interprets as trying to include two different values, the first of which is a directory.
Even if you don't have a comment there, any trailing whitespace will be preserved. When editing makefiles you should try to put your editor into a mode where it flags trailing whitespace, or even better removes it automatically. GNU Emacs, for example, can do this.

Can GNU make handle spaces?

I have a makefile that has C INCLUDES with spaces in them. There is no way for me to get around having to have the spaces in the file names. Is there any way to have spaces in file names with gnu make?
Make has some basic support for this by escaping spaces in filenames, in that the following Makefile will correctly compile and recompile the C file foo bar.c:
foo\ bar: foo\ bar.c
gcc -o "${#}" "${<}"
However, you have to be super-careful in quoting every command you run, and variables that are space-separated lists of files—e.g., SRCS, LIBS—won’t work, although it’ß possible that with enough hacking using Make text functions you can parse out the quotes and get everything working…
So while there is rudimentary support for spaces in filenames in rules and patterns, anything complicated is going to be an awful lot of very hard and frustrating work.

Trailing comments after variable assignment subvert comparison

In GNU make, trailing comments appended to variable assignments prevent subsequent comparison (via ifeq) from working correctly.
Here's the Makefile...
A = a
B = b ## trailing comment
C = c
RESULT :=
ifeq "$(A)" "a"
RESULT += a
endif
ifeq "$(B)" "b"
RESULT += b
endif
ifeq "$(C)" "c"
RESULT += c
endif
rule:
#echo RESULT=\"$(RESULT)\"
#echo A=\"$(A)\"
#echo B=\"$(B)\"
#echo C=\"$(C)\"
Here's the output...
$ make
RESULT=" a c"
A="a"
B="b "
C="c"
As you can see from the displayed value of RESULT, the ifeq was affected by the presence of the comment in the assignment of B. Echoing the variable B, shows that the problem is not the comment, but the intervening space.
The obvious solution is to explicitly strip the whitespace prior to comparison like so...
ifeq "$(strip $(B))" "b"
RESULT += b
endif
However this seems error prone. Since the strip operation is not needed unless/until a comment is used, you can leave out the strip and everything will initially work just fine -- so chances are you won't always remember to add the strip. Later, if someone adds a comment when setting the variable, the Makefile no longer works as expected.
Note: There is a closely related issue, as demonstrated in this question, that trailing whitespace can break string compares even if there is no comment.
Question: Is there a more fool-proof way to deal with this issue?
This is not something particular to GNU Make; rather, make is defined by POSIX to work this way:
string1 = [string2]
The macro named string1 is defined as having the value of string2, where string2 is defined as all characters, if any, after the <equals-sign>, up to a comment character (#) or an unescaped <newline>. Any <blank> characters immediately before or after the <equals-sign> shall be ignored.
This can be construed as a feature allowing you to clearly create variables with trailing whitespace:
FOO = stuff # this macro has two trailing spaces
BAR = something else# and this one has none
though probably usually it would be clearer to reorganise the places you use $(FOO) rather than depend on it having obscure whitespace.
Probably the best way to deal with this is just to avoid it: have a convention that you do not put comments on variable definition lines (except very occasionally to make intentional whitespace explicit). Instead of writing this:
A = a # list of apples
B = b # list of bananas
C = c # list of carrots
write this:
# list of apples
A = a
# list of bananas
B = b
# list of carrots
C = c
This tends to be the style in GNU projects (see for example the bottom of this page), though I don't recall whether this is documented anywhere.
Incidentally, when examining whitespace you probably want to quote your variables in your echo command more:
rule:
#echo 'RESULT="$(RESULT)"'
In your echo RESULT=\"$(RESULT)\" version, $(RESULT) is not quoted from the shell, so tabs and multiple spaces are being misleadingly displayed as single spaces.
Here are some raw ideas that I have:
Make it a policy to always use strip with ifeq
Not using strip would be a rare exception and would require an explanation in the comments.
Don't manually set configuration variables inside of a Makefile
Find or create some other tool to do that.
maybe the POSIX shell will suffice (although I think the nuances of shell variables may be worse than those of make).
I suspect that the GNU build system (Autoconf/Automake/etc.) addresses this, but my feeling is that this is overkill for most purposes.
Use some kind of "lint" tool to find these kind of problems
I'm not aware of the existence of any such tool.
Modify GNU make to fix this problem.
Preferably minimizing the impact on existing Makefiles.
Modify the make language so that by default the trailing-spaces are stripped
Use a more modern build tool instead of GNU make
Ugly, but perhaps more foolproof. Anyone who edits this in the future might at least notice that you, perhaps, made it ugly on purpose.
A = $(strip a )##
B = $(strip b )## trailing comment
C = $(strip c )##

GNU Make: How to call $(wildcard) within $(eval)

I'm trying to create a generic build template for my Makefiles, kind of like they discuss in the eval documentation.
I can't seem to get the wildcard function to work within an eval. The basic code I'm having issues with looks like this.
SRC_DIR = ./src/
PROG_NAME = test
define PROGRAM_template
$(1)_SRC_DIR = $(join $(SRC_DIR), $(1)/)
$(1)_SRC_FILES = $(wildcard $$($(1)_SRC_DIR)*.c)
endef
$(eval $(call PROGRAM_template, $(PROG_NAME)))
all:
#echo $(test_SRC_DIR)
#echo $(test_SRC_FILES)
#echo $(wildcard $(wildcard $(test_SRC_DIR)*.c)
When I run make with this, the output is
./src/test
[correct list of all .c files in ./src/test/]
Basically, the wildcard call within PROGRAM_template is not being eval'd as I expect it. The call results in an empty list.
The join call is being eval'd correctly though.
So, what am I doing wrong? My guess is that
$$($(1)_SRC_DIR)
is not correct, but I can't figure out the right way to do it.
EDIT
Once this was solved, it didn't take long for me to hit another issue with eval.
I posted it as a new question at
Workaround for GNU Make 3.80 eval bug
You need to double escape virtually all of the functions and variables when you use eval. In most cases, the only things that don't need to be double-escaped are function arguments (because the call function will fully expand them). In this case, you technically don't need to double-escape join or SRC_DIR either, but it will simplify your life if you just always double-escape all variables and functions when using eval.
The reason you need the double escapes is that expansion happens twice when using eval. The eval function itself performs expansion, and then expansion is done again when the block is finally parsed as makefile syntax (i.e. when it is actually evaluated).
The way you've got it written, wildcard is invoked on the string literal $( test_SRC_DIR)*.c. If you want, you can see this for yourself by replacing wildcard with info in your version and see what happens.
You need to hold off on actually invoking wildcard until the second expansion, so that it's argument is the result of the expansion of $(test_SRC_DIR).
Try this:
SRC_DIR = ./src/
PROG_NAME = test
define PROGRAM_template
$(1)_SRC_DIR = $$(join $$(SRC_DIR),$(1)/)
$(1)_SRC_FILES = $$(wildcard $$($(1)_SRC_DIR)*.c)
endef
$(eval $(call PROGRAM_template,$(PROG_NAME)))
all:
#echo $(test_SRC_DIR)
#echo $(test_SRC_FILES)
#echo $(wildcard $(test_SRC_DIR)*.c)
EDIT: After posting this, I thought I'd better test it out to make sure it actually works. In doing so, I discovered another problem. You should avoid putting spaces between the comma and argument when calling functions. It causes a literal space character to be prepended to the argument that is passed to the function and leads to unintended results. I've removed the spaces after the commas in the function calls in my version (while this isn't a problem for the call to join, I removed the space there as well just because it's a good habit to get into).

Resources