What's the meaning of $#? - shell

I came across the makefile when I read something about flex
fb3-1: fb3-1.l fb3-1.y fb3-1.h
bison -d fb3-1.y
flex -ofb3-1.lex.c fb3-1.l
cc -o $# fb3-1.tab.c fb3-1.lex.c fb3-1funcs.c
but what's the meaning of $#? Is it in the shell or some argument of gcc?

$# is just short-hand for the file name of the current target (fb3-1 in this case).
See the Automatic Variables section of the gnu make manual for full details on this and other useful automatic variables such as $<.

Related

GNU Make expand pattern within shell command

I have a situation where I need a small program to determine the prerequisites of a file by examining it. Concretely:
%.bar: $(shell python get_preqs.py %.foo) # % in shell is not substituted
python gen_bar.py $^ $#
This doesn't work however, as the % in the shell command doesn't get substituted in by make. Is there any way to achieve this?
You can use the .SECONDEXPANSION feature of GNU make:
.SECONDEXPANSION:
%.bar: $$(shell python get_prereqs.py $$*.foo)
python gen_bar.py $^ $#
Another option is to use a build tool more suited to dynamic targets. For example, I've written a Gnu Make-like tool incorporating some of the concepts from DJB's ReDo, called GoodMake. Your equivalent makefile would just be:
#? *.bar
preqs=$(python get_preqs.py ${1%.bar}.foo)
$0 $preqs
python gen_bar.py $preqs $1

dividing outputs in make by filename

I am processing some files and want to at one point create two categories depending on the filename so I can compare the two. Is this possible in a makefile?
%.output1: %.input
ifneq (,$(findstring filename1,$(echo $<)))
mv $<.output1 $#
endif
%.output2: %.input
ifneq (,$(findstring filename2,$(echo $<)))
mv $<.output2 $#
endif
%.output_final: %.output1 %.output2
do_something
I think there is two things wrong with this code:
There is a mistake in the ifneq line.
%.output1 %.output2 will always use the same filename - it may not be possible to do this in 'make' and this may require ruffus.
You have tab-indented the ifneq line so make doesn't consider it a make directive and is considering it a shell command and attempting to pass it to the shell to execute (hence the shell error you removed in your recent edit).
Use spaces (or no indentation) on that line to have make process it correctly. That being said having done that you cannot use $< in the comparison as it will not be set at that point.
$(echo) is also not a make function. You have mixed/confused processing times. You cannot combine make and shell operations that way. (Not that you need echo there to begin with.)
If you want the comparison to happen at shell time then do not use make constructs and instead use shell constructs:
%.output1: %.input
if [ filename1 = '$<' ]; then
mv $<.output1 $#
fi
Though that is also incorrect as $< is %.input and $# is %.output1 for whatever stem matched the %. That rule should probably look more like this (though I'm having trouble understanding what you are even trying to have this rule do so I may have gotten this wrong).
%.output1: %.input
# If the stem that matched the '%' is equal to 'filename1'
if [ filename1 = '$*' ]; then
# Then copy the prerequisite/input file to the output file name.
cp $< $#
fi
I'm not sure I understand your second question point. The % in a single rule will always match the same thing but between rules it can differ.
This %.output_final: %.output1 %.output2 target will map the target file foo.output_final to the prerequisite files foo.output1 and foo.output2. But will also map any other *.output_final file to appropriately matching prerequisite files.

Using bash variables in Makefile

I want to use the bash timing variables in my makefile
for example in my terminal I can do this and it works
MY_TIME=$SECONDS
echo $MY_TIME
but when I write this on my makefile it does not work
how can I use these two lines in my make file?
this is what I'm doing
.PHONY: myProg
myProg:
MY_TIME=$SECONDS
echo $MY_TIME
After Etan Reisner' answer
This is what I have now
.PHONY: myProg
myProg:
MY_TIME= date; echo $MY_TIME
but the result of my echo is an empty line, it does not look like it is storing the date
the dollar sign ($MY_TIME) refers to make variables, which are not the same as bash variables.
To access a bash variable you must escape the dollar using the double dollar notation ($$MY_TIME).
.PHONY: myProg
myProg:
MY_TIME=$$SECONDS ; echo $$MY_TIME
As already mentioned in Etan answer you can't split the code into multiple lines (unless you are using the backslash) since each command executes in a different subshell, making variables inaccessible to other lines.
In the following example the value of SECONDS will be always 0, since it get reset by the spawn of the shell for the second line.
.PHONY: myProg
myProg: # WRONG
MY_TIME=$$SECONDS
echo $$MY_TIME
By default make uses /bin/sh as the shell which executes recipe lines.
Presumably /bin/sh doesn't support the SECONDS variable.
You can tell make to use a different shell by assigning a value to the SHELL variable (i.e. SHELL := /bin/bash).
Doing that will make SECONDS available but will still not allow you to carry a variable value between recipe lines as each recipe line is run in its own shell.
So to do what you want you would need to write both of those lines on one line or continue the line over the newline.
.PHONY: myProg
myProg:
MY_TIME=$SECONDS; echo $MY_TIME
or
.PHONY: myProg
myProg:
MY_TIME=$SECONDS; \
echo $MY_TIME
That being said you would almost certainly be better off not doing this and instead using something like date invoked at the start/end of the recipe or time invoked on the command to be timed directly instead.
.PHONY: myProg
myProg:
date
# Do something
date
or
.PHONY: myProg
myProg:
time some_command
PROGRAM_NAME = myLittleProgram
...
$(PROGRAM_NAME) : myLittleProgram.o
I know the above works, as it is in my own makefile (program names and object names changed to protect the innocent).
"Variable references can be used in any context: targets, dependencies, commands, most directives, and new variable values. Here is an example of a common case, where a variable holds the names of all the object files in a program:"
objects = program.o foo.o utils.o
program : $(objects)
cc -o program $(objects)
$(objects) : defs.h
http://web.mit.edu/gnu/doc/html/make_6.html

Source shell script into makefile

I have a bash shell script which I usually source into my shell, with lots of environment variables defined, which are not exported. I do not want to:
Export the variables, because this would make the exportable environment too big, and eventually make the whole system slower (it must be exported when running every command from the shell)
Redefine those variables in the makefile (DRY)
I would like to source the same shell script into the environment of the makefile, so that I can access those variables. Is this possible? How can I do that? Ideally I would do in the makefile:
source setup-env.sh
There is not source command for makefiles, but maybe something equivalent? Any special hack I can use to simulate the same effect?
As per the additional question in the comment, here is one way to effectively mark the whole environment as exported:
for var in $(compgen -v); do export $var; done
compgen -v simply outputs all variable names, as per the bash manual, section 8.7 Programmable Completion Builtins. Then we simply loop over this list and export each one.
Credit to https://stackoverflow.com/a/16337687/2113226 - compgen is new to me.
There are two ways I can think of to integrate this into your make workflow:
- Shell script wrapper
Simply write a shell script which sources your setup-env.sh, exports all variables as above, then calls make itself. Something like:
#!/bin/bash
./source setup-env.sh
for var in $(compgen -v); do export $var; done
make $#
- Recursive make
It may be that you don't want a shell script wrapper, and want to directly invoke make for whatever reason. You can do this all in one Makefile which calls itself recursively:
$(info MAKELEVEL=$(MAKELEVEL) myvar=$(myvar))
ifeq ($(MAKELEVEL), 0)
all:
bash -c "source ./setup-env.sh; \
for var in \$$(compgen -v); do export \$$var; done; \
$(MAKE) $#"
else
all: myprog
myprog:
echo "Recipe for myprog. myvar=$(myvar)"
endif
Output for this Makefile is:
$ make
MAKELEVEL=0 myvar=
bash -c "source ./setup-env.sh; \
for var in \$(compgen -v); do export \$var; done; \
make all"
MAKELEVEL=1 myvar=Hello World
make[1]: Entering directory `/home/ubuntu/makesource'
echo "Recipe for myprog. myvar=Hello World"
Recipe for myprog. myvar=Hello World
make[1]: Leaving directory `/home/ubuntu/makesource'
$
We check the GNU Make builtin variable MAKELEVEL to see what level of recursion we are at. if the level is 0, then we recursively call make for all targets, but first source ./setup-env.sh and export all variables. If the recursion level is anything else, we just do the normal makefile stuff, but you see that the variables you need are now available. This is highlighted by the $(info ) line at the top of the Makefile, which shows the recursion level, and the value (or not) of myvar.
Notes:
We have to use bash -c because compgen is strictly a bash builtin, and not available in Posix mode - i.e. when make invokes the shell as sh -c by default.
The $ in the first all: recipe need to be escaped very carefully. The $$ escapes the $ from being expanded by make, and the \$$ escapes the $ from being expanded by the implicit sh
There is plenty of literature arguing that "Recursive make is considered harmful". E.g. http://aegis.sourceforge.net/auug97.pdf

How does $# work in a make conditional?

I'm using GNU Make 3.80. In my Makefile, I use automatic variable $# to refer to the current target.
#echo current target is ... [$#]
ifeq ($#,sms)
#echo yep, they are equal
else
#echo no, they are not equal
endif
It seems that $# expands to sms, as shown in the output below.
Output is:
current target is ... [sms]
no, they are not equal
My question: since $# (apparently) expands to sms, shouldn't the "true" branch of the ifeq conditional be executed (with the consequence that the output should read yep, they are equal)? [I am at a loss as to why the output is no, they are not equal.]
From the GNU Make manual:
10.5.3 Automatic Variables
...
It's very important that you recognize the
limited scope in which automatic
variable values are available: they
only have values within the recipe.
... there is a special feature of GNU
make, secondary expansion (see
Secondary Expansion), which will allow
automatic variable values to be used
in prerequisite lists.
That is, $# can only be used inside the section containing commands to build the target and, with some restrictions, inside the prerequisites list.
However you can use shell commands to implement conditions inside the list of commands used to build the target:
#echo current target is ... [$#]
if [[ "$#" == "sms" ]]; then \
echo yep, they are equal; \
else \
echo no, they are not equal; \
fi
Also if you want to check what targets were specified on the command line use MAKECMDGOALS variable.

Resources