Pass variable to make CFLAGS in shell script? - bash

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.

Related

GNU make use variable value to call a "function"

I want to iterate through the list, LIST, and want to call a function which writes something into a text file using the name of the extracted list elements as name of the call to the "function".
.PHONY: GEN_BAT
GEN_BAT:
for comp in $(basename $(notdir $(LIST))); do \
echo comp: $$comp ;\
$(call $$comp)
done; \
If I write the line $(call $$comp) without a ;\ I'll receive "syntax error: unexpected end of file".
If I write the line with the original function e.g. $(call FUNCTION);\ with a ;\ it calls the existing function as expected. So, something is wrong with the used variable?
How can I persuade make to use the value of $$comp as a call to "function"?
It's not clear at all what you want to do; providing a complete, minimal example would help a lot.
However, it's not possible to use a shell loop then try to utilize the shell loop variable in a make function. If you think about it that doesn't make any sense: the shell is a completely different program than make, make can't know what the shell's variable are set to, and the shell can't call make functions.
Make runs a command like this: first the entire recipe is expanded so that ALL make variables and functions are resolved. Then make takes that expanded command string and gives it to the shell and the shell process it as a shell script. Make waits for the shell to complete, then reports the exit code as success (0) or failure (not 0).
Also, a statement like $(call xyz) makes no real sense in make: calling a function with no arguments is identical to simply expanding the variable as in $(xyz). The only time call makes sense is if you pass arguments to it.

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.

get the command line parameters from within a Makefile

I'm trying to get the command line parameters used to invoke make utility inside a Makefile. Example:
make -C /some/folder -f someMakeFile.mk SOME_VAR=someValue
Inside someMakeFile.mk, I would like to get my hands on the following string:
-C /some/folder -f someMakeFile.mk
or
-C /some/folder -f someMakeFile.mk SOME_VAR=someValue
I'm not interested in SOME_VAR value, that is already well known. I'm interested in the rest of the command line switches for make utility itself (-C and -f).
The reason I'm asking is because I would like to slightly alter the behavior of someMakeFile.mk when certain cmd line switches are used. For example, I would like to return dummy values from complex macro definitions when make is invoked in dry-run mode, rather than do the actual work. This would speed up the TAB auto-completion in bash when you type make than TAB+TAB to show the list of targets.
Or, if this is not possible, is there a way (a variable or function) available for someMakeFile.mk to detect that is only a dry-run?
Best regards
You can check the MAKEFLAGS variable to see if the first word contains an n (for dry run):
ifeq (n,$(findstring n,$(firstword $(MAKEFLAGS))))
$(info in dry-run mode)
else
$(info not in dry-run mode)
endif

Makefile define variable using if

I'm trying to do something like it
#if[[ 1==1 ]] then;\
COMPILER_CMD = -fPic;\
fi;
But if i call in the next line the variable it don't work.
If i define it outside the if it works perfect.
Someone can help me?
As everyone is saying, you haven't given us enough information. But I'll make a guess. You want to set this variable conditionally, then use it elsewhere in the makefile, and in other makefiles which include this one.
The trouble is that you are trying to use shell syntax. In a command this will work (if the syntax is correct), but the value will apply only in that command. Outside commands, shell syntax is just wrong and will cause an error, malfunction, or be ignored depending on exactly what you do.
Try this in the makefile, outside of any rule (that is, not in the recipe for any particular target):
ifeq (1,1)
COMPILER_CMD = -fPic
endif
$(info $(COMPILER_CMD))
If that works, then you can try to adapt it to do whatever it is you're actually trying to do.
Each line in the Makefile is executed separately in a new shell process, so that's why changes you made to the environment are not propagated to next line.
You can combine both lines into one long one to achieve what you want. You probably have something like this in you Makefile:
#if[[ 1==1 ]] then;\
COMPILER_CMD = -fPic;\
fi;
echo $COMPILER_CMD
You want to add the line continuation backslash to the line before echo:
#if[[ 1==1 ]] then;\
COMPILER_CMD = -fPic;\
fi; \
echo $COMPILER_CMD
I'm assuming that the example you show is the recipe for some rule. By the syntax here it looks like you're trying to set a make variable COMPILER_CMD from within a recipe based on the value of some shell boolean test, which is of course impossible. You have to be very clear in your mind how make works: make is not interpreting the recipes you write, in any way. Make is simply passing those recipes to another program (the shell) and the other program is interpreting those commands. Thus, you can't change the behavior of make, including setting make variables, from within a recipe: that recipe is being run in a completely different program.
As others have said, you don't give enough information about what you REALLY want to do, at a higher level, for us to give a complete solution. Having a boolean like 1==1 doesn't give any hint whatsoever as to why you're doing this. Also your shell syntax contains syntax errors, so we can tell you didn't actually cut and paste this from a real, working example.
You can, as piokuc implies, use a shell variable COMPILER_CMD (you have to remove the whitespace around the = to make it a shell variable assignment) but that value takes effect only while that one recipe line is running. For the next recipe line a new shell is started and any values set in the previous shell are lost:
all:
# if [[ 1 == 1 ]]; then COMPILER_CMD=-fpic; fi; \
echo COMPILER_CMD=$$COMPILER_CMD
# echo COMPILER_CMD=$$COMPILER_CMD
will give:
COMPILER_CMD=-fpic
COMPILER_CMD=

Writing contents of makefile (>131000 chars) variable to a file?

How can I write the contents of a makefile variable to file, without invoking a shell command?
The problem is that the contents of the variable is possible longer than the shell allows for a command (i.e. longer than MAX_ARG_STRLEN (131072) characters).
In particular, in a makefile I have a variable containing a long list of filenames to process (including their absolute pathes for out-of-source builds). Now I need to write those filenames to a (temporary) file, which I can then pass to another command.
So far, we had a rule like ($COLLATED_FILES is the variable containing the paths):
$(outdir)/collated-files.tely: $(COLLATED_FILES)
$(LYS_TO_TELY) --name=$(outdir)/collated-files.tely --title="$(TITLE)" \
--author="$(AUTHOR)" $^
This breaks if COLLATED_FILES is longer than about 130000 characters, we get the error message:
make[2]: execvp: /bin/sh: Argument list too long
As a solution, we are now trying to write the contents of the variable to a file and use that file in the $(LYS_TO_TELY) command. Unfortunately, I have not yet found a way to do this without invoking the shell.
My attempts include:
$(outdir)/collated-files.list: $(COLLATED_FILES)
echo "" > $#
$(foreach f,$^,echo $f >> $#;)
But this also invokes all echo commands at once in a shell, so the shell command is just as long.
Is there any way to write the contents of $(COLLATED_FILES) to a file on disk without passing them on the command line to a shell command?
I also searched whether I could pipe the contents of the variable to the shell, but I couldn't find anything in that direction, either...
Assuming you are using GNU Make, there is the file function!
https://www.gnu.org/software/make/manual/html_node/File-Function.html
$(file op filename,text)
where op is either > or >>.
This requires GNU Make 4.0+
You could move whatever makefile code you use to build up the value of COLLATED_FILES to a trivial helper makefile, then invoke make recursively from your original makefile and use trivial shell redirection to capture the stdout of the recursive make invocation -- basically using make as a rudimentary text-processing tool in that context. For example, create a makefile called get_collated_files.mk with these contents:
COLLATED_FILES=abc
COLLATED_FILES+=def
COLLATED_FILES+=ghi
# ... etc ...
# Use $(info) to print the list to stdout. If you want each filename on a
# separate line, use this instead:
#
# $(foreach name,$(COLLATED_FILES),$(info $(name)))
$(info $(COLLATED_FILES))
all: ;##shell no-op to quell make complaints
Then, in your original makefile:
collated-files.list:
$(MAKE) -f get_collated_files.mk > $#
$(outdir)/collated-files.tely: collated-files.list
$(LYS_TO_TELY) --name=$(outdir)/collated-files.tely --title="$(TITLE)" \
--author="$(AUTHOR)" --filelist=collated-files.list
This will be quite a lot more efficient than using hundreds or thousands of individual echo invocations to append to the file one path at a time.
EDIT: One final option, if you really want to have each filename on a separate line, and you have a lot of control over how COLLATED_FILES is defined:
define NL
endef
COLLATED_FILES=abc
COLLATED_FILES+=$(NL)def
COLLATED_FILES+=$(NL)ghi
$(info $(COLLATED_FILES))
all: ;##no-op
This approach allows you to again use just one call to $(info), if that's important to you for some reason.
Here's a patch to gnu make that lets you directly write a variable into a file. It creates a new 'writefile' function, similar to the existing 'info' function, except it takes a filename argument and writes to the file:
https://savannah.gnu.org/bugs/?35384
It looks to me as if you should rethink your build design-- surely there's a better way than letting a variable get this big. But here's a way to do it:
# Make sure this doesn't collide with any of your other targets.
NAMES_TO_WRITE = $(addprefix write_,$(COLLATED_FILES))
collated-files.list: $(NAMES_TO_WRITE)
write_blank:
echo "" > collated-files.list
.PHONY: $(NAMES_TO_WRITE)
$(NAMES_TO_WRITE) : write_% : write_blank
echo $* >> collated-files.list

Resources