Makefile target name compared to string - makefile

In a makefile I'm trying to compare the target name with a string, and depending on this set a variable with a string or another.
This example illustrates what I'm trying to do:
ifeq ($#,"Target_A")
THE_PATH="Path_a"
THE_TARGET=$#
else
THE_PATH="Path_b"
THE_TARGET=$#
endif
Target_A:
#echo $(THE_PATH)
#echo $(THE_TARGET)
Target_B:
#echo $(THE_PATH)
#echo $(THE_TARGET)
This is the output when I call make passing Target_A and when I call it passing Target_B:
$ make Target_A
Path_b
Target_A
$ make Target_B
Path_b
Target_B
The fact that I always get "Path_b" indicates the ifeq always evaluates to false, but you can see that $# contained the right string.
Why doesn't this work?

You probably want target-specific variables:
Target_A: THE_PATH="Path_a"
Target_A:
#echo $(THE_PATH)
Since contents of a (regular) variable are expanded each time it's used, THE_TARGET=$# can be made global.
Target-specific variables are only accesible in a target they belong to, and its dependencies.
Normally this is enough, but if you need to have global variables, you can use the same code you have in the question, with the condition changed to this:
ifneq ($(filter Target_A,$(MAKECMDGOALS)),)
$# (which you tried to use) only works inside of a recipe, and expands to a target name that the recipe builds.
$(MAKECMDGOALS) is a global variable that contains all targets specified (as command-line parameters) when invoking make.
This option will only work if the target you're looking for was specified as a command-line parameter.

Related

Defining makefile variable at build time and using it in foreach

I am defining a makefile variable by parsing a json file using jq.
define variable
$(eval objects := $(shell jq -c '.abc.values[]' config.json))
endef
target_a:
$(variable)
#echo "from target-a: $(objects)"
target_b: $(foreach X, $(objects), stage_$(X))
I am getting an output that's the same as the json list:
from target-a: first second
This means the variable is getting initialized correctly but the foreach is not triggering any stage_% targets. Why is that?
Basically your makefile cannot work. Make proceeds in two distinct steps: first all makefiles are entirely parsed and an internal graph of the targets and prerequisites is created. Second make walks that graph and invokes all the recipes. All prerequisites must be defined during the first step. All recipes are only expanded during the second step.
Here, you've added the expansion of the objects variable into the recipe for target_a, so that will not happen until the second step, IF make decides to rebuild the target target_a because it's out of date. But, the definition of the prerequisites of target_b has already happened a long time ago, when make was parsing the makefile in the first step.
You can do this in your example and it will work:
objects := $(shell jq -c '.abc.values[]' config.json)
target_a:
#echo "from target-a: $(objects)"
target_b: $(foreach X, $(objects), stage_$(X))
Here, objects is expanded as the makefile is parsed and so it will have a value before target_b is expanded.
Maybe there's some reason you can't do it this way, but if so you've simplified your example too much for us to help.

How do you use (GNU) make's "simply expanded" variables in build rules and not get the last definition?

I have a complicated set of rules I need to write to generate a rather large number of "parameterised" output files and thought that, rather than expand them all out by hand, I could repeatedly "include" a template file with sets of rules and use (GNU)make's facility for allowing "simply expanded" variables to avoid the pain.
(In the past I've always been using the "recursively expanded" variable approach, so this is new to me)
As a trivial example of what I thought would work, I tried putting the following in a Makefile
Targ:=A
Param1:=Pa
Param2:=Qa
$(Targ):
#echo expect A, get $(Targ), Target is $#. Params are $(Param1) and $(Param2)
Targ:=B
Param1:=Pb
Param2:=Qb
$(Targ):
#echo expect B, get $(Targ), Target is $#. Params are $(Param1) and $(Param2)
Targ:=C
Param1:=Pc
Param2:=Qc
$(Targ):
#echo expect C, get $(Targ), Target is $#. Params are $(Param1) and $(Param2)
The eventual plan was to replace the rules with an include file containing dozens of different rules, each referencing the various "parameter" variables.
However, what I get is...
prompt> make A
expect A, get C, Target is A. Params are Pc and Qc
prompt> make B
expect B, get C, Target is B. Params are Pc and Qc
Essentially, unlike each rule's target, which is picking up the intended definition, the $(Targ), $(Param1), and $(Param2) in each rule's command is instead being run with the final definition.
Does anyone know how to prevent this, i.e. how do you force the command to use the definition at the time it is encountered in the Makefile?
Simple vs recursive expansion makes no difference here; regardless of which you use you'll see the same behavior. A GNU make variable is global and obviously can have only one value.
You have to understand when variables are expanded. The documentation provides a detailed description of this. Targets and prerequisites are expanded when the makefile is read in, so the value of Targ as the makefile is being parsed is used.
Recipes are expanded when the recipe is to be invoked, which is not until after all makefiles are parsed and make starts to build targets. At that time of course the variable Targ has its last set value.
Without knowing what your makefile really does it's hard to suggest an alternative. One option is to use target-specific variables:
Targ := A
$(Targ): LocalTarg := $(Targ)
$(Targ):
#echo expect A, get $(LocalTarg), Target is $#
Another option is to use constructed variable names:
Targ := A
Targ_$(Targ) := $(Targ)
$(Targ):
#echo expect A, get $(Targ_$#), Target is $#
Apologies for answering my own question, but I now realised it is possible to solve the issue I was having by running make recursively.
E.g. if the parameter variables for the rules are Targ, Param1 and Param2 then
#Set up "default" values for the parameters (As #madscientist points out,
#these will safely be overridden by the defs on the #(make) commands below
Targ=XXXXXXXXXX
Param=XXXXXXXXXX
Param2=XXXXXXXXXX
Recursing=
#
# define N (==3) templated rule(s)
#
$(Targ)%a:
#echo Run Combo_a $(Targ) $(Param1) $(Param2) $#
$(Targ)%b:
#echo Run Combo_b $(Targ) $(Param2) $(Param1) reversed $#
$(Targ)%c:
#echo Run Combo_c $(Param1) $(Targ) $(Param2) mixed again $#
#
#Enumerate "M" (==2) sets of parameters,
# (Except if we are already recursing because unrecognised targets may cause
# it to descend forever)
#
ifneq ($(Recursing), Yes)
Set1%:
#$(MAKE) Targ=Set1 Param1=foo Param2=bar Recursing=Yes $#
Set2%:
#$(MAKE) Targ=Set2 Param1=ray Param2=tracing Recursing=Yes $#
endif
This then allows N*M different combos for N+M typing cost.
eg. (removing messages from make re recursion)
>make Set1.a
Run Combo_a Set1 foo bar Set1.a
>make Set2.c
Run Combo_c ray Set2 tracing mixed again Set2.c

Makefile - use the same variable twice in a single row

I have a recipe:
define postmake
ZZZ=$(strip $(1))
echo $(ZZZ) $(ZZZ)
endef
$(1) is the first argument passed to it. When the recipe is called the echo command prints only the first expansion of the variable. The second one is lost. How can I expand/use the same variable twice in the same row?
I need it for objcopy something.elf -O binary something.bin (something is the variable, in my case the second file becomes just .bin and is obviously wrong).
UPDATE: I use Boilermake as the template. Everything else besides this recipe works perfectly fine. I invoke the postmake recipe like this:
TARGET :=main.elf
TGT_POSTMAKE := ${postmake}$(TARGET)
It turns out that in Boilermake when specifying TGT_POSTMAKE only the recipe name can be defined.
Correct way:
TGT_POSTMAKE := $(postmake)
Incorrect way:
TGT_POSTMAKE := $(postmake) $(TARGET)
Then in the postmake recipe instead of arguments like $(1) simply $(TARGET) can be used.

Using ifeq and ifndef in GNU Make

I've written a fairly simple test Makefile where I define two targets, all & clean. I've got two different conditional statements. One checks for the existence of the $(MAKECMDGOALS) special variable and the other detects whether any of the command line targets matched those listed in a variable (NODEPS). The problem I'm having is that none of the branches within my conditionals get executed. Ultimately I want to use a conditional to decide whether the target I'm supplying should include some autogenerated dependency files but at the moment I'm struggling to get either expression to even evaluate. I'm running GNU make version 3.81 and I've tried it under Ubuntu and Mac OS X to no avail.
NODEPS := clean
INCLUDE = $(filter $(NODEPS),$(MAKECMDGOALS))
.PHONY : all clean
ifndef $(MAKECMDGOALS)
#echo "$$(MAKECMDGOALS) is not defined"
else
#echo "$(MAKECMDGOALS) is defined"
endif
ifneq (0, $(words $(INCLUDE)))
#echo "INCLUDE = $(INCLUDE) != 0"
else
#echo "INCLUDE = $(INCLUDE) == 0"
endif
all :
#echo "all : $(MAKECMDGOALS)"
clean :
#echo "clean : $(MAKECMDGOALS)"
I eventually managed to work out what was wrong. #eriktous was right, pointing out that I should be using $(info) rather than #echo. More subtly though, part of the problem was that I'd indented the #echos with a tab. It seems that tabs are mandatory for rules but not allowed in conditionals. The other mistake was I'd expanded the $(MAKECMDGOALS) variable in the test condition when it should have been written as just ifndef MAKECMDGOALS.
https://www.gnu.org/software/make/manual/html_node/Make-Control-Functions.html
A makefile is not a shell script. You can not "randomly" place executable statements anywhere you like and expect them to be executed.
There are various ways of communicating with the outside world from within a makefile: $(info ...), $(warning ...), $(error ...) and $(shell #echo ...) (some or all of these may be GNU make extensions).
Ps: you misspelled PHONY.

Passing additional variables from command line to make

Can I pass variables to a GNU Makefile as command line arguments? In other words, I want to pass some arguments which will eventually become variables in the Makefile.
You have several options to set up variables from outside your makefile:
From environment - each environment variable is transformed into a makefile variable with the same name and value.
You may also want to set -e option (aka --environments-override) on, and your environment variables will override assignments made into makefile (unless these assignments themselves use the override directive . However, it's not recommended, and it's much better and flexible to use ?= assignment (the conditional variable assignment operator, it only has an effect if the variable is not yet defined):
FOO?=default_value_if_not_set_in_environment
Note that certain variables are not inherited from environment:
MAKE is gotten from name of the script
SHELL is either set within a makefile, or defaults to /bin/sh (rationale: commands are specified within the makefile, and they're shell-specific).
From command line - make can take variable assignments as part of his command line, mingled with targets:
make target FOO=bar
But then all assignments to FOO variable within the makefile will be ignored unless you use the override directive in assignment. (The effect is the same as with -e option for environment variables).
Exporting from the parent Make - if you call Make from a Makefile, you usually shouldn't explicitly write variable assignments like this:
# Don't do this!
target:
$(MAKE) -C target CC=$(CC) CFLAGS=$(CFLAGS)
Instead, better solution might be to export these variables. Exporting a variable makes it into the environment of every shell invocation, and Make calls from these commands pick these environment variable as specified above.
# Do like this
CFLAGS=-g
export CFLAGS
target:
$(MAKE) -C target
You can also export all variables by using export without arguments.
The simplest way is:
make foo=bar target
Then in your makefile you can refer to $(foo). Note that this won't propagate to sub-makes automatically.
If you are using sub-makes, see this article: Communicating Variables to a Sub-make
Say you have a makefile like this:
action:
echo argument is $(argument)
You would then call it make action argument=something
From the manual:
Variables in make can come from the environment in which make is run. Every environment variable that make sees when it starts up is transformed into a make variable with the same name and value. However, an explicit assignment in the makefile, or with a command argument, overrides the environment.
So you can do (from bash):
FOOBAR=1 make
resulting in a variable FOOBAR in your Makefile.
It seems command args overwrite environment variable.
Makefile:
send:
echo $(MESSAGE1) $(MESSAGE2)
Example run:
$ MESSAGE1=YES MESSAGE2=NG make send MESSAGE2=OK
echo YES OK
YES OK
There's another option not cited here which is included in the GNU Make book by Stallman and McGrath (see http://www.chemie.fu-berlin.de/chemnet/use/info/make/make_7.html). It provides the example:
archive.a: ...
ifneq (,$(findstring t,$(MAKEFLAGS)))
+touch archive.a
+ranlib -t archive.a
else
ranlib archive.a
endif
It involves verifying if a given parameter appears in MAKEFLAGS. For example .. suppose that you're studying about threads in c++11 and you've divided your study across multiple files (class01, ... , classNM) and you want to: compile then all and run individually or compile one at a time and run it if a flag is specified (-r, for instance). So, you could come up with the following Makefile:
CXX=clang++-3.5
CXXFLAGS = -Wall -Werror -std=c++11
LDLIBS = -lpthread
SOURCES = class01 class02 class03
%: %.cxx
$(CXX) $(CXXFLAGS) -o $#.out $^ $(LDLIBS)
ifneq (,$(findstring r, $(MAKEFLAGS)))
./$#.out
endif
all: $(SOURCES)
.PHONY: clean
clean:
find . -name "*.out" -delete
Having that, you'd:
build and run a file w/ make -r class02;
build all w/ make or make all;
build and run all w/ make -r (suppose that all of them contain some certain kind of assert stuff and you just want to test them all)
If you make a file called Makefile and add a variable like this $(unittest)
then you will be able to use this variable inside the Makefile even with wildcards
example :
make unittest=*
I use BOOST_TEST and by giving a wildcard to parameter --run_test=$(unittest)
then I will be able to use regular expression to filter out the test I want my Makefile
to run
export ROOT_DIR=<path/value>
Then use the variable, $(ROOT_DIR) in the Makefile.

Resources