Variables with function calls as dependencies in GNU Make - gnu-make

Is it possible to use generated variables as dependencies inside a GNU Make rule? I am trying to do
A := 1 2 3
B := a b c
C := $(foreach X,$(A),$(foreach Y,$(B),$X-$Y))
a:
echo A
b:
echo B
$(C) : $(word 2,$(subst -, ,$#))
echo $#
I would like to be able to run
make 2-a
and get echo A and echo $#, that is, I want $(word 2,$(subst -, ,$#)) to be evaluated as dependency list. However, it seems like it is evaluated before the rules are matched.
Is it possible, without an external program, to have variables with function calls as dependencies?

The gods of GNUmake foresaw your desires and implemented .SECONDEXPANSION:
A := 1 2 3
B := a b c
C := $(foreach X,$(A),$(foreach Y,$(B),$X-$Y))
a:
echo A
b:
echoechoecho B
.SECONDEXPANSION:
$(C) : $$(word 2,$$(subst -, ,$$#))
echo $#
All rules which appear after this label will be subject to a second expansion of their prerequisite list, which means that you can use automatic variables like $#which are undefined during the first expansion.
All you have to take into consideration is that you need to quote every $ (i.e. make it a $$) that shall spring into effect only during the second expansion of the prerequisite list.

Related

Make: run common pre/post rules for a set of targets

I want to build the following dependency graph, but with pre and post being artifact free:
Before creating/updating any of a, b, or c the command pre should run once and afterwards post should run once. Both do not and preferably should not produce artifacts. And of course, these should only be run if any of a b c have changed. This should all be triggered by a phony all target, i.e. a is never run independently.
Using order-only prerequisites a: | pre does not help because these are always run. Making post depend on a b c won't work because then it is also run all the time because post does not create an artifact.
If this is impossible and artifacts are required after all, how would pre (the more interesting of the two) only run if any of the targets which depend on it have changed?
Note: a etc. are normal makefile targets (which could be called independently), e.g.:
a: a.in a.dependency
#echo Creating a
#mkabc a.in > a
There is only one way in make to force a command to execute before target X is built, but only if target X needs to be built, and that's put the command as the first thing in the recipe for target X. There's no way to manipulate the dependency graph in GNU make so that make determines if a target needs to be built and, if so, first builds some other target before the recipe runs.
So you will definitely have to use recursive make here, putting the command to build the pre target into the recipe of the other targets. However, of course that will cause it to be built multiple times which you don't want.
One way to get around that is to play a trick using eval. Try this (untested):
BUILD_PRE = $(shell $(MAKE) -j1 pre >/dev/null)
post: a b c
echo $#
pre:
echo $#
a b c:
$(BUILD_PRE)$(eval BUILD_PRE =)
touch $#
.PHONY: pre post
In the rule for a, b, and c we first expand the BUILD_PRE variable which results in a recursive make invocation via the shell call. Then the eval expansion will reset the value of BUILD_PRE so that it's now empty; this means in subsequent rules for b and c this first line will expand to the empty string and pre will not be run again.
You may ask, why do we need to use shell here? Can't we just use:
BUILD_PRE = $(MAKE) -j1 pre
so that the first recipe contained a recursive make? The problem with this is that it won't work with parallel make. Suppose the first target make attempts to build is a (it will always be of course). That recipe will contain a recursive make invocation and make will start it. But if you are using -j make does not wait for that recipe to complete: it will go try to start b and c. Since BUILD_PRE is now empty you only get one build of pre, but b and c are not waiting for pre to be completed.
By using a shell function the recursive invocation is forced to complete when the recipe is expanded, before any other recipe is started.
I should say, I suspect there may be a few odd things about this. In particular when make normally invokes a recursive build it does some setup etc. that won't happen when a recursive build is invoked through shell. But, it should work.
Edit: Final Makefile with '+' prefix to mark recursive make calls:
all: allabc
BUILD_PRE = $(shell $(MAKE) pre)
BUILD_POST =
pre:
#echo PRE abc >&2
post:
#echo POST abc >&2
allabc: a b c
#+$(BUILD_POST) > /dev/null
a:
+$(BUILD_PRE)$(eval BUILD_PRE = )
touch "$#"
$(eval BUILD_POST = $$(MAKE) post)
b:
+$(BUILD_PRE)$(eval BUILD_PRE = )
touch "$#"
$(eval BUILD_POST = $$(MAKE) post)
c:
+$(BUILD_PRE)$(eval BUILD_PRE = )
touch "$#"
$(eval BUILD_POST = $$(MAKE) post)
clean:
rm -f a b c
Not sure I understand all the details but assuming you want to build your 5 targets when invoking make all, with the dependencies you show (and maybe a, b and c in parallel), you can, for instance:
.PHONY: all pre post
all:
$(MAKE) pre
$(MAKE) a b c
$(MAKE) post
pre:
<pre-recipe>
post:
<post-recipe>
a:
<a-recipe>
...

How can you export a variable to Make's 'shell' function?

Consider the following:
$ cat a.sh
#!/bin/sh
echo in a.sh, BANANA=$BANANA
$ cat Makefile
.PHONY: foo
export BANANA = I am a banana
foo:
$(eval F=`./a.sh`) # BANANA is set in a.sh
echo $F
$(eval G=$(shell ./a.sh)) # BANANA is *not* set in a.sh
echo $G
$ make
# BANANA is set in a.sh
echo `./a.sh`
in a.sh, BANANA=I am a banana
# BANANA is *not* set in a.sh
echo in a.sh, BANANA=
in a.sh, BANANA=
As demonstrated, the export directive to Make tells make to set the variable BANANA in the environment of its children. But that setting does not apply to the shell function. It does seem to apply to the backticks. Is this a bug? How can I easily set make variables to the environment of the shell function?
Note:
$ make --version
GNU Make 4.0
Built for x86_64-unknown-linux-gnu
Copyright (C) 1988-2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
First, be clear about the meaning of your recipe:
foo:
$(eval F=`./a.sh`) # BANANA is set in a.sh
echo $F
$(eval G=$(shell ./a.sh)) # BANANA is *not* set in a.sh
echo $G
This recipe contains two (not four) commands:
foo:
echo $F
echo $G
and the two make-functions:
$(eval F=`./a.sh`)
$(eval G=$(shell ./a.sh))
will be evaluated, in that order, for the scope of the two-line recipe when make decides to
run it. If you are surpised by this point, read this question
and answer.
Be clear also that F and G are both make variables, not shell variables. You
only get away with referring to $F and $G rather than $(F) and $(G) thanks
to the last para of 6.1 Basics of Variable References
A dollar sign followed by a character other than a dollar sign, open-parenthesis or open-brace
treats that single character as the variable name. Thus, you could reference the variable x with ‘$x’.
However, this practice is strongly discouraged, except in the case of the automatic variables
It wouldn't work for, say, FF and GG.
So the normal way to write your makefile would be:
.PHONY: foo
export BANANA = I am a banana
foo: F=`./a.sh`
foo: G=$(shell ./a.sh)
foo:
echo $(F)
echo $(G)
which has exactly the same effect.
And this perhaps clarifies the difference between the output of echo $(F)
and echo $(G).
$(shell ./a.sh) invokes a make function that executes ./a.sh in a shell
directly spawned by make and returns the stdout of so doing. Thus for target foo, make-variable G will
be defined as the stdout of executing ./a.sh in a child shell of make.
`./a.sh` does not invoke any make-function. As far as make is concerned, it is just
a string. For the target foo, make-variable F will be defined as `./a.sh `
The exported make-variable BANANA is not injected into the environment of a shell spawned by
$(shell ...). 5.7.2 Communicating Variables to a Sub-make
To pass down, or export, a variable, make adds the variable and its value to the
environment for running each line of the recipe
An exported variable and its definition is only injected into the environments of the shells that
run the lines of recipes.
Thus BANANA is not defined in the environment of a.sh when it is run by $(shell ./a.sh)
to generate the definition of G. But it is defined in the environment of the shell
that that runs the recipe line echo $(F), with $(F) = `a.sh`. That shell (not make) interprets
`a.sh` as a back-tick invocation of a subshell, which inherits the definition of
BANANA.
To get BANANA exported into the environment of $(shell ...), you have to do
it yourself since it is not done by make:
G=$(shell export BANANA='$(BANANA)'; ./a.sh)

How does gmake parse and execute template definitions

So, I have a make template. I invoke it like:
$(eval $(call PRIVATE_LIBRARY_TEMPLATE,privatelib1,64))
And it is defined like:
define PRIVATE_LIBRARY_TEMPLATE
# Evaluate the condition multiple times because of way make processes templates
$(if $(2)=='',$(eval $(call LIBRARYBUILD_TEMPLATE,$(1),32)))
$(if $(2)=='',$(eval $(call LIBRARYBUILD_TEMPLATE,$(1),64)))
$(if $(2)!='',$(eval $(call LIBRARYBUILD_TEMPLATE,$(1),$(2))))
# More stuff that doesn't matter here
endef
or like:
define PRIVATE_LIBRARY_TEMPLATE
# Evaluate the condition multiple times because of way make processes templates
$(if $(2)=='',$(call LIBRARYBUILD_TEMPLATE,$(1),32))
$(if $(2)=='',$(call LIBRARYBUILD_TEMPLATE,$(1),64))
$(if $(2)!='',$(call LIBRARYBUILD_TEMPLATE,$(1),$(2)))
# More stuff that doesn't matter here
endef
previously it was defined as:
define PRIVATE_LIBRARY_TEMPLATE
$$(eval $$(call LIBRARYBUILD_TEMPLATE,$(1),32))
$$(eval $$(call LIBRARYBUILD_TEMPLATE,$(1),64))
before I add the $(if, $(1) is passed to the LIBRARYBUILD_TEMPLATE intact, but once I add the $(if, $(1) becomes an empty string.
I've tried various combinations of $$ $(eval $$eval etc, but there is something fundamental I'm just not understanding about the way gmake is parsing this template definition.
What I am trying to do is to make $(2) optional in this template, and use it if provided, or if not provided build both 32 and 64 bit libraries.
How is the template definition being initially parsed, and then evaluated.
First off the function $(if condition,then-part,else-part) is not the same as the other conditionals like ifeq. The $(if) function condition is simply checked for empty string or not (c.f. manual):
If it expands to any non-empty string, then the condition is considered to be true. If it expands to an empty string, the condition is considered to be false.
When it comes to $(eval $(call ...), things can become tricky in which order things must be evaluated. I usually think like this:
If the evaluated result of an operation depends on an argument (like $1), then the operation needs to be delayed with secondary expansion.
So in your case I think this is what you want:
.SECONDEXPANSION:
define LIBRARYBUILD_TEMPLATE
$$(info >> LIBRARYBUILD_TEMPLATE: $1 $2)
endef
define PRIVATE_LIBRARY_TEMPLATE
$$(info > PRIVATE_LIBRARY_TEMPLATE $1 $2)
ifeq ($2,)
$$(eval $$(call LIBRARYBUILD_TEMPLATE,$1,32))
$$(eval $$(call LIBRARYBUILD_TEMPLATE,$1,64))
else
$$(eval $$(call LIBRARYBUILD_TEMPLATE,$1,$2))
endif
endef
$(eval $(call PRIVATE_LIBRARY_TEMPLATE,privatelib1,64))
$(eval $(call PRIVATE_LIBRARY_TEMPLATE,privatelib2,))
Which gives the output:
> PRIVATE_LIBRARY_TEMPLATE privatelib1 64
>> LIBRARYBUILD_TEMPLATE: privatelib1 64
> PRIVATE_LIBRARY_TEMPLATE privatelib2
>> LIBRARYBUILD_TEMPLATE: privatelib2 32
>> LIBRARYBUILD_TEMPLATE: privatelib2 64
Let's use this definition of PRIVATE_LIBRARY_TEMPLATE
define PRIVATE_LIBRARY_TEMPLATE
# Evaluate the condition multiple times because of way make processes templates
$(if $(2)=='',$(eval $(call LIBRARYBUILD_TEMPLATE,$(1),32)))
$(if $(2)=='',$(eval $(call LIBRARYBUILD_TEMPLATE,$(1),64)))
$(if $(2)!='',$(eval $(call LIBRARYBUILD_TEMPLATE,$(1),$(2))))
endef
It is instructive to look in detail at what make does when it encounters
$(eval $(call PRIVATE_LIBRARY_TEMPLATE,privatelib1,64))
Clearly, before it can expand the $eval, make must first expand the $call:
1 is set to privatelib1
2 is set to 64
PRIVATE_LIBRARY_TEMPLATE is now expanded.
First off, a $(if …) needs expanding:
Make looks at the condition in $(if $(2)=='',$(call LIBRARYBUILD_TEMPLATE,$(1),32)), and so expands $(2)==''. You will note that 64=='' not an empty string, and so is considered true. To complete the expansion of the $(if …) , make thus chooses the true branch and goes on to expand $(call LIBRARYBUILD_TEMPLATE,$(1),32)
1 becomes privatelib1
2 becomes 32
$(call LIBRARYBUILD_TEMPLATE,privatelib1,32) becomes some text. No idea what, but since it will eventually be passed to $eval it must be valid make syntax. Let's assume it is something simple like LIB_privatelib1_32 := 1.
A second $(if …) is similarly expanded.
A third $(if …) is similarly expanded.
For the sake of argument, let's say the final expansion of the $(call PRIVATE_LIBRARY_TEMPLATE,…) is this text:
LIB_privatelib1_32 := 1
LIB_privatelib1_64 := 1
LIB_privatelib1_64 := 1
These three lines are passed to $eval.
As a side-effect, three new simple variables are defined.
The expansion of the $(eval …) though is empty.
Phew.
One obvious mistake here is that == is not valid make syntax.
Truthyness is merely whether a string has characters in it.
You probably want something like:
$(if $2,$(eval $(call LIBRARYBUILD_TEMPLATE,$1,32)))
$(if $2,$(eval $(call LIBRARYBUILD_TEMPLATE,$1,64)))
$(if $2,,$(eval $(call LIBRARYBUILD_TEMPLATE,$1,$2)))
(check out that last one for negations.)

UNIX command line argument referencing issues

I'm trying to tell unix to print out the command line arguments passed to a Bourne Shell script, but it's not working. I get the value of x at the echo statement, and not the command line argument at the desired location.
This is what I want:
./run a b c d
a
b
c
d
this is what I get:
1
2
3
4
What's going on? I know that UNIX is confused as per what I'm referencing in the shell script (the variable x or the command line argument at the x'th position". How can I clarify what I mean?
#!/bin/sh
x=1
until [ $x -gt $# ]
do
echo $x
x=`expr $x + 1`
done
EDIT: Thank you all for the responses, but now I have another question; what if you wanted to start counting not at the first argument, but at the second, or third? So, what would I do to tell UNIX to process elements starting at the second position, and ignore the first?
echo $*
$x is not the xth argument. It's the variable x, and expr $x+1 is like x++ in other languages.
The simplest change to your script to make it do what you asked is this:
#!/bin/sh
x=1
until [ $x -gt $# ]
do
eval "echo \${$x}"
x=`expr $x + 1`
done
HOWEVER (and this is a big however), using eval (especially on user input) is a huge security problem. A better way is to use shift and the first positional argument variable like this:
#!/bin/sh
while [ $# -gt 0 ]; do
x=$1
shift
echo ${x}
done
If you want to start counting a the 2nd argument
for i in ${#:2}
do
echo $i
done
A solution not using shift:
#!/bin/sh
for arg in "$#"; do
printf "%s " "$arg"
done
echo

In Unix, is it possible to give getops a range of values to expect?

Sorry if the title is confusing, but here's what I mean:
If I have a script that can accept several parameters, I'd use the getops command in order to more easily control script actions based on the parameters passed. However, lets say one of these parameters can be any number from 5 - 9, or whatever. Is there a way to tell getops that any number passed as command to the script between 5 and 9 should be taken as a single user-command?
My code so far is something like:
#!/bin/sh
args=`getopt -o abc: -- "$#"`
eval set -- "$args"
echo "After getopt"
for i in $args
do
case "$i" in
-c) shift;echo "flag c set to $1";shift;;
-a) shift;echo "flag a set";;
-b) shift;echo "flag b set";;
done
I want to see if I can do something like:
#!/bin/sh
args=`getopt -o ab[0-9]c: -- "$#"`
eval set -- "$args"
echo "After getopt"
for i in $args
do
case "$i" in
-c) shift;echo "flag c set to $1";shift;;
-a) shift;echo "flag a set";;
-b) shift;echo "flag b set";;
-[0-9]) shift; echo $i;;
done
No, at least not with the one I use (someone may have an enhanced one out there but it won't be standard anywhere).
For that particular case, it's probably okay to use:
args=`getopt -o ab0123456789c: -- "$#"`
although, for larger cases, that might be unwieldy.
Have you thought about not treating them as individual options? In other words, say they're debug levels for a logging procedure. Why could you not use:
args=`getopt -o abc:d: -- "$#"`
and specify them with progname -b -d4 instead of progname -b -4?

Resources