GNU Makefile substitution reference issue: using % twice - makefile

I am trying to use GNU Make's substitution reference, but I need to refer to the % twice in the substitution. And it is not working the way I expected. Here is my Makefile:
foo := io protocol util
bar := $(foo:%=../Lib%/obj/lib%.a)
default: ; #echo bar = $(bar)
I want end up with:
bar = ../Libio/obj/libio.a ../Libprotocol/obj/libprotocol.a ../Libutil/obj/libutil.a
But this is what I end up with:
../Libio/obj/lib%.a ../Libprotocol/obj/lib%.a ../Libutil/obj/lib%.a```
I've read the GNU Make reference manual, and I don't see any mention or examples of using % twice in one statement. I don't even know if it is possible.
Any help would be greatly appreciated.

As you have discovered by reading the manual, it's not possible to use the pattern twice. Only the first is expanded: the rest are simply considered literal characters.
You can use the foreach function instead:
bar := $(foreach F,$(foo),../Lib$F/obj/lib$F.a)

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

What does obj_$(variable_name) do in a makefile?

I have encountered the following line in a make file:
OBJECTDIR = obj_$(TARGET)
What does the command obj_$(TARGET) do?
Unfortunately this command is not listed in the GNU make manual:
https://www.gnu.org/software/make/manual/
This is just normal variable expansion. Say that the TARGET variable has value foo, e.g. somewhere before that line you have:
TARGET := foo
Then obj_$(TARGET) will be expanded to obj_foo, which is equivalent to:
OBJECTDIR = obj_foo
I suggest you look up how to use variables in the Make manual (the expansion will work differently depending on the flavor of TARGET).

Using computed names in makefile to emulate namespaces (fx target local)

My idea is to use computed names to emulate namespaces, for example:
GetVar = $(or $($2#$1),$($2))
fubar#namespace = foo bar
frob = baz
$(info $(call GetVar,namespace,fubar))
$(info $(call GetVar,namespace,frob))
furthermore this could be used in rules to have target local variables
cmd#target = echo hello
target:
$(call GetVar,$#,cmd)
This seem to work on GNU-make, but it raises a few questions:
First of all this will result in using non-normal characters in variable names. Of course one may use another separator than #, but in using the target name as the namespace name you would inevitably run into cases where dots and slashes end up in the variable name. How bad is this? The GNU-make manual tells me to avoid this and mentions the ability to share this via environment to submakes, but this is in a non-recursive makefile setup.
Is this exploiting a non-portable feature of GNU-make? Need I be concerned with feature versions of make breaking this feature?
Is there any example of this idiom actually being used? Not being able to find any cases makes me wonder if there's anything I've missed that makes this a bad idea.
If it's not that bad idea, are there any particular namespace separator that would be more suitable than other?
As far as I can see you're mostly recreating target-specific variables. Is there some reason you don't want to use them?
target: cmd = echo hello
target:
$(cmd)

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

Makefile mysteries: What is wrong with this particular use of pathsubst?

My makefile contains these snippets (among others):
SRC = src
OBJ = obj
DEPS = $(wildcard $(SRC)/*.cpp)
# ...
all : $(BINARIES)
#echo $(pathsubst $(SRC)/%.cpp,$(OBJ)/%.d,$(DEPS))
#echo $(DEPS:$(SRC)/%.cpp=$(OBJ)/%.d)
When I make all, only the second #echo outputs something:
$ make all
obj/sample1.d obj/sample1_U.d
The (gnu make) manual states:
Another type of substitution reference lets you use the full power of the patsubst function. It has the same form ‘$(var:a=b)’ described above, except that now a must contain a single ‘%’ character. This case is equivalent to ‘$(patsubst a,b,$(var))’
From this explanation, I would expect that both #echo statements produce the same output, which they clearly don't. What is wrong with the first form using the explicit pathsubst?
(I am using gnu make 3.81 on OS X.)
Presumably you want patsubst, not pathsubst.

Resources