Variables in makefiles (How to use arrays) - makefile

I have compiler flag in a makefile which looks like:
MYVARIABLE1=HELLO 4.5.6
MYVARIABLE2"="{0x95,0x04,0x05,0x06,' ','A','A'}"
CFLAGS+=-DMYVARIABLE2=${MYVARIABLE2}
which works fine. But if I want to use already known info in VARIABLE1 to create VARIABLE2:
MYVARIABLE1=HELLO 4.5.6
MYVARIABLE2"="{0x95,$(MYVARIABLE1[7]},$(MYVARIABLE1[9]},$(MYVARIABLE1[11]},' ','A','A'}"
CFLAGS+=-DMYVARIABLE2=${MYVARIABLE2}
But when I run my makefile with the second option, It stops compile at the c-file using the CFLAG with the error message:
error: expected expression before ',' token
in the C-code:
uint8 OTHERVARIABLE[] = MYVARIABLE2;
Question: Is the really $(MYVARIABLE1[x ]}the correct way of using parts of a variable defined in the makefile?

There is no such thing as an "array" or list variable in make syntax.
There are some GNU make functions which will operate on every word of a string, one at a time, where a word is a space-separated value. This can be thought of as an array, kind of. This is why make has an almost impossible time dealing with paths containing whitespace.
If you're not using GNU make, then none of this will work.
To do what you want you'll have to split the MYVARIABLE1 value into separate words, then you can access individual words using functions. Something like this will work:
MYVARIABLE1 = HELLO 4.5.6
__var1 = $(subst ., ,$(MYVARIABLE1))
MYVARIABLE2 = "{0x95,$(word 2,$(__var1)),$(word 3,$(__var1)),$(word 4,$(__var1)),' ','A','A'}"
The subst function replaces the . with spaces, giving a result of HELLO 4 5 6 so that you can reference the parts of the version individually using the word function.
You can find out more about GNU make functions in the documentation.

Is the really $(MYVARIABLE1[x]} the correct way of using parts of a variable defined in the makefile?
It is valid bash syntax, but not valid GNU make syntax.

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

In makefile, how to store multi-line shell output in variable

I have a shell command where it outputs multiple lines. I want to store it in a variable in makefile for later processing in the target.
A simplified example:
I have this file called zfile1
#zfile1
some text
$echo 123
more text
$$$#&^$
more text
The makefile:
a:
#$(eval v1 = $(shell cat zfile1))
# need to process the variable here, example:
#echo "$(v1)"
# I want to prevent expansion of values in the file and print in multi-line
If you have GNU make 4.2 or above you can use the $(file <zfile1) function. See https://www.gnu.org/software/make/manual/html_node/File-Function.html
If you don't have a new-enough version of GNU make, you can't do it. Of course in your example there's no real need to put the contents of the file into a make variable at all: you can just have your recipe use the file itself. But maybe your real use-case isn't so simple.
ETA
You should never use either the make function eval or the make function shell in a recipe [1].
You can just write:
v1 := $(file <zfile1)
.ONESHELL:
a:
#echo "$(v1)"
You must have the .ONESHELL because otherwise each line of the recipe (after it expands into multiple lines) is considered a separate recipe line. Note that .ONESHELL is in effect for the entire make process so could cause other recipes to break if they rely on each line being invoked in a different shell.
Another option is to export the result into the environment, and use a shell variable like this:
export v1 := $(file <zfile1)
a:
#echo "$$v1"
There are probably better ways to do it but since you've only given us this example and not what you really want to do, that's about all we can say.
[1] There are times where it can be useful but if you have a sufficiently sophisticated requirement to need this you'll probably know how to use it.
I think you're making things too complicated.
Start by writing your recipes as proper self-contained shell scripts.
You can then either store the whole script in a file and run it from make, or you can include it directly in your makefile as a single logical line, as in the following:
a:
#v1=$$(< zfile1); \
echo $$v1
Note the need to "escape" the dollar sign by repeating it.
You could also use global make variables, depending on the actual logic of your real-world use.

Pass variable to make CFLAGS in shell script?

I'm trying to build some c programs with gnu make (4.1) through a shell script. I want to use different CFLAGS parameters, which I read from a file. The file contains lines like:
"-O1 -freorder-functions"
I am trying to do something like
while read line
do
opts="$line"
make CFLAGS="$opts"
done < $1
but all my attempts end up in the following error:
cc1: error: argument to ‘-O’ should be a non-negative integer, ‘g’, ‘s’ or ‘fast’
So I tried to change the file's contents to only the parameters after -O1 (-freorder-functions), and add the -O1 in the shell script:
opts=$(printf "\"-O%d %s\"", 1, "$line")
(and a lot of other things that seem less sensible) but I still get the same error message.
Hard-coding the parameter to make in the shell script works fine:
make CFLAGS="-O1 -freorder-functions"
Sorry if this is a stupid question, but I can't seem to find any examples of how something like this is done, and I'm new to shell scripting, so I don't really understand how it's treating the variables here. An echo of what I'm attempting to pass to CFLAGS looks okay to me.
With double quotes around the flags in the file and you dutifully quoting your shell variables properly, your make call ends up being
make CFLAGS="\"-O1 -freorder-functions\""
That is to say, with double quotes in the flags. This is passed all the way down to the compiler call, which means that the compiler call is something like
cc "-O1 -freorder-functions" -c foo.c
...which asks the compiler to use optimization level 1 -freorder-functions, and it complains that that is not valid.
To solve this, I would remove the quotes from the file. You could also use opts=$line with $line unquoted, but that is not exactly safe.

Generating list of generated sources (a la foreach) in automake

I am currently working on a project using templates quite extensively, and running into memory constraints during instantiation. I have split up the instantiation into a number of very simple files, which are all three-liners consisting of includes only.
I let these be generated by a rule in Makefile.am. Now I have a quite long list of files that should be generated in my Makefile, and would like to refactor this list with a foreach-like expression. In more specific terms: I have a line like
libfoo_la_SOURCES = a_0.cpp a_1.cpp ... b_0.cpp b_1.cpp ... c_0.cpp c_1.cpp ...
which could be more concisely expressed as
libfoo_la_SOURCES = $(foreach i,a b ...,$(foreach j,0 1 ...,$i_$j.cpp))
However, the second construct is not only warned against by automake, but also does not work: The files given in this manner are neither compiled nor cleaned.
My current workaround is generating this file list by a shell script.
Any ideas how to implement this iteration?
I would forget about making loops: the GNU extension is not standard, and not understood by Automake. One standard (and portable) make construction you can use here is the macro expansion with substitution: $(var:subst1=subst2) will expand to the value of $(var) after replacing any suffix subst1 of a word by subst2. Automake understands this.
If subst1 is empty, as in $(var:=subst2), you are appending subst2 to all files in $(var). You can use this to construct your list of files as follows:
f = a b c d e f
g = $(f:=_0) $(f:=_1) $(f:=_2) $(f:=_3)
all_files = $(g:=.cpp)
echo:
#echo $(all_files)
Running make echo with the above Makefile will display all files from a_0.cpp to f_3.cpp.
Like you, I discovered that the GNU make foreach function will not work like this because
the sources need to be there at the time the Makefile is generated. So, I use GNU Autogen (also here) to generate a makefile fragment which is subsequently included in Makefile.am. So it's probably not that different than your shell script approach.

How do I process extremely long lists of files in a make recipe?

Because GNU make allows variables to be as large as memory allows, it has no problem building massive dependency lists. However, if you want to actually use these lists of files in a recipe (sequence of shell commands for building a target), you run into a problem: the command might exceed the shell's command line length limit, producing an error such as "Argument list too long".
For example, suppose I want to concatenate several files contained in the list $(INPUTS) to produce a file combined.txt. Ordinarily, I could use:
combined.txt: $(INPUTS)
cat $^ > $#
But if $(INPUTS) contains many thousands of files, as it does in my case, the call to cat is too long and fails. Is there a way to get around this problem in general? It's safe to assume that there exists some sequence of commands that have identical behaviour to the one enormous command -- in this case, a series of cat commands, one per input file, that use >> to append to combined.txt would work. But how can make be persuaded to generate those commands?
In looking for the answer, about the best suggestion I could find was to break up the list into a series of smaller lists and process them using shell for loops. But you can't always do that, and even when you can it's a messy hack: for example, it's not obvious how to get the usual make behaviour of stopping as soon as a command fails. Luckily, after much searching and experimentation, it turns out that a general solution does exist.
Subshells and newlines
make recipes invoke a separate subshell for each line in the recipe. This behaviour can be annoying and counterintuitive: for example, a cd command on one line will not affect subsequent commands because they are run in separate subshells. Nevertheless it's actually what we need to get make to perform actions on very long lists of files.
Ordinarily, if you build a "multiline" list of files with a regular variable assignment that uses backslashes to break the statement over multiple lines, make removes all newlines:
# The following two statements are equivalent
FILES := a b c
FILES := \
a \
b \
c
However, using the define directive, it's possible to build variable values that contain newlines. What's more, if you substitute such a variable into a recipe, each line will indeed be run using a separate subshell, so that for example running make test from /home/jbloggs with the makefile below (and assuming no file called test exists) will produce the output /home/jbloggs, because the effect of the cd .. command is lost when its subshell ends:
define CMDS
cd ..
pwd
endef
test:
$(CMDS)
If we create a variable that contains newlines using define, it can be concatenated with other text as usual, and processed using all the usual make functions. This, combined with the $(foreach) function, allows us to get what we want:
# Just a single newline! Note 2 blank lines are needed.
define NL
endef
combined.txt: $(INPUTS)
rm $#
$(foreach f,$(INPUTS),cat $(f) >> $#$(NL))
We ask $(foreach) to convert each filename into a newline-terminated command, which will be executed in its own subshell. For more complicated needs, you could instead write out the list of filenames to a file with a series of echo commands and then use xargs.
Notes
The define directive is described as optionally taking a =, := or += token on the end of the first line to determine which variable flavour is to be created -- but note that that only works on versions of GNU make 3.82 and up! You may well be running the popular version 3.81, as I was, which silently assigns nothing to the variable if you add one of these tokens, leading to much frustration. See here for more.
All recipe lines must begin with a literal tab character, not the 8 spaces I have used here.

Resources