Trailing comments after variable assignment subvert comparison - comments

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 )##

Related

gnu make hash symbol in the middle of string

I'm trying to print out the perforce file version of the make file when it's executed. I'm using the $Id$ tag, which expands to $Id: //repository/path/check.make#6 $ or the like and I want to print //repository/path/check.make#6 to a file (currently using echo). I can't figure out how to get make to take the # as part of the string and not the beginning of a comment. I tried:
str1 = $(subst \#,\\\#,'$Id: //repository/path/check.make#6 $')
and other variations but I keep getting:
unterminated call to function `subst': missing `)'. Stop.
It would help if you provided a full example of what you want. I don't really understand why you're trying to subst a hash with a backslash hash. If you showed us a full example, including how you get the string and also what you want to do with the variable ar1, we could actually give you advice.
But, the way to use hashes in GNU make is to put them into a variable:
HASH := \#
$(info HASH = $(HASH))
That's all I can say without more info.
ETA
Yes, I'm very familiar with keyword expansion... it originated with SCCS/RCS back in the day :).
I see, you mean, you want to put the $Id$ into your makefile, then when your makefile is checked out the value will be replaced. That wasn't clear to me.
I'm sorry to say that what you want to do is close to impossible. The problem is that you can't escape the value in the makefile because you're not writing the value into the makefile, Perforce is. And Perforce is not escaping it.
You have only two options that I can see:
First, don't try to put this into a make variable. There are many ways to do this, depending on what you really want. One way is to create a header file that contains const char* foo = "$Id$"; and let that be replaced. If you really want the ID of the makefile, but you only need it within a certain recipe, you can put it directly into that recipe:
myrecipe: ; echo '$$Id$$'
(I'm not actually sure the $$ trick here will work, it depends on how Perforce replaces things... if it doesn't you can use echo '$Id$x' you'll lose the dollar signs but keep the rest).
The only other option is to upgrade your version of GNU make to the latest (4.3). In that release, some broken handling of hash characters in the $(shell ...) function was fixed, which means you can use:
var1 := $(shell echo '$$Id$$')
and it will work (same caveats, and solutions, for $$ here as above).
Maybe I didn't get you correctly but the following works for an outside actor replacing $Id$ without escaping:
define PERFORCE_ID
$Id$
endef
PERFORCE_ID := $(word 2,$(value PERFORCE_ID))
$(info $(PERFORCE_ID))
As a test, I simply put in the text substitution from Perforce myself:
define PERFORCE_ID
$Id: //repository/path/check.make#6 $
endef
PERFORCE_ID := $(word 2,$(value PERFORCE_ID))
$(info Perforce id is: $(PERFORCE_ID))
Output:
Perforce id is: //repository/path/check.make#6
You can't have an unescaped literal # in a make assignment and not have it be interpreted as a comment character. But as a hack, you can have the shell extract this token from the current Makefile.
# $Id: //repository/path/check.make#6 $
str1 := $(shell sed '/[$$]Id[$$:]/!d;s/^\# [$$]Id: \(.*\) [$$].*/\1/' Makefile)
The sed script looks for the $Id$ or $Id: token in the Makefile itself by way of a regex which doesn't match itself; the doubled dollar sign is how you put a literal dollar sign in a Makefile. It extracts the contents of the field, and make assigns the output to str1. Because there is no literal # in the code which assigns the variable, no further escaping is necessary.
Demo: https://ideone.com/hWjnCp
This requires GNU Make, but that's apparently what you are using already. (Please tag such questions explicitly as gnu-make.)

Makefile string with spaces

var := 'C:/some/path here' labas
all:
#echo $(words $(var))
This returns 3.
How to make it return 2? Or make does not work the way I think??
Make definitely doesn't work the way you think, if you think that make has any interest in quotes of any type :). Make ignores (i.e., treats as normal characters like any other) both single- and double-quote characters in virtually every situation, and all make functions work in simple whitespace delimiters without regard to quote characters.
In general, it's not practical to use make with any system that requires spaces in target names. It just doesn't work: too much of make is based on whitespace as a separator character and there's no general, common way to escape the whitespace that works everywhere.
In your very particular case you can play a trick like this:
E :=
S := $E $E
path := C:/some/path here
xpath := $(subst $S,^,$(path))
var := $(xpath) labas
all:
#echo $(words $(var))
#echo $(subst ^,$S,$(var))
Basically, this creates a variable $S which contains a space, then it substitutes all spaces in the variable path with some other, non-space character (here I chose ^ but you can pick what you like, just note it has to be a character which is guaranteed to not appear in the path).
Now this path doesn't contain whitespace so you can use it with make functions.
This has major downsides: not only the complexity visible here, but note these paths cannot be used in target names since they won't actually exist on disk.
In general, you should simply not use paths containing whitespace with make. If you must use paths containing whitespace, then very likely make is not the right tool for you to be using.

Two assignments on same line in GNU makefile

A certain makefile (namely, the otherlibs/num/Makefile in the OCaml 4.2 distribution) has the following line :
EXTRACFLAGS=-DBNG_ARCH_$(BNG_ARCH) -DBNG_ASM_LEVEL=$(BNG_ASM_LEVEL)
I am surprised to see two assignments on the same line here. What does it mean ? Is it the same as
EXTRACFLAGS=-DBNG_ARCH_$(BNG_ARCH)
-DBNG_ASM_LEVEL=$(BNG_ASM_LEVEL)
I did not find anything about that in the GNU make manual, maybe I am unaware of the right keyword to search this.
There are not two assignments here. The line assigns the definition:
-DBNG_ARCH_$(BNG_ARCH) -DBNG_ASM_LEVEL=$(BNG_ASM_LEVEL)
to the make variable EXTRACFLAGS. The value appears to consist
of flags that are to be passed to some invocation of the C preprocessor to
define macros BNG_ARCH_$(BNG_ARCH) and BNG_ASM_LEVEL. E.g. as
in a make recipe:
$(CPP) $(EXTRACFLAGS) ...
Which, supposing the make assignments:
BNG_ARCH = foo
BNG_ASM_LEVEL = bar
would expand to:
cpp -DBNG_ARCH_foo -DBNG_ASM_LEVEL=bar ...
OP comments:
So you might say that = is "left-associative", the leftmost = sign is the one that really gets executed. Is this documented somewhere in the manual?
From my frequent, but far from exhaustive resorts to manual I can't say it is documented. Somebody
may know better. There is no formal grammar there, but it is everywhere illustrated that the definition of a variable extends from the
start of the non-whitespace text following =, := or ?= to the end of the (possibly \-extended)
logical line. You may satisfied on the point by running the makefile:
.PHONY: all
x = X y = Y
all:
#echo [$(x)]
#echo [$(y)]
which prints:
[X y = Y]
[]
Assignment signs within a make-definition are not assignments, they're just characters in the definition.

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.

How to comment sh scripts in Make

Make's semantics force complex sh scripts to be broken up using \. Make turns those recipes into (arbitrarily long) one-liners. Therefore, I can't use # for comments. The Make function $(info my comment goes here) works, but the comment is printed even when the recipe itself isn't invoked.
I would like to have something like rem "string" in sh.
What I have been using so far is $(call rem, my comment goes here) in Make,
which I define as:
rem = $(if,$(1))
But I'd like to not reinvent the wheel if something already exists and I just missed it. In particular, in sh I'd like something that preserves pipes, or at the very least stdout.
You can use the shell's do-nothing operator, which is :. So like:
all:
#echo "hi" ; : this is a comment ; echo "there"
Just be aware that the shell does expand these, so if you want to use special characters like quotes, backticks, etc. you should escape them from the shell if you're worried they'll cause problems.
ETA:
If you want something that won't interfere with shell behaviors like pipelines, you can't do that with any shell construct; all possible methods will cause syntax errors in the shell.
You'll have to use a make construct to force make to get rid of the text before it invokes the shell. The example you have kind of works, but not for the reason you think. If you really wanted to use a user-defined function rem then to run it you'd need to invoke call, as in:
$(call rem, my comment goes here)
What your method is doing is, since there is no function rem defined, it's looking up the variable named rem my comment goes here and that variable doesn't exist, so it expands to the empty string. You can see this for yourself by having your rem function actually invoke $(info ...) or something: it will never be printed.
So you can continue to use the $(rem ...) syntax but you might as well remove the rem = $(if,$(1)) since it's not used anyway, and will just confuse people. Or you could use something smaller like $(: my comment goes here) or whatever. Really you can put anything in there as long as it doesn't expand to a valid variable name.
I should point out that this is not necessarily future-proof; since variable names containing whitespace are not allowed anymore in make, it's possible that make will decide to handle a variable reference containing whitespace in a different way, sometime in the future.

Resources