Environment variable escaping across multiple Makefiles - makefile

What's the correct way to pass and modify environment variables through several levels of makefiles? Specifically I'm setting LDFLAGS in the parent makefile using the special $ORIGIN RPATH value, and I need to add to the variable in some of the child makefiles. The linker wants the literal string $ORIGIN, that's not another variable to expand.
Makefile:
# default value for all children
export LDFLAGS = -Wl,-rpath='$$ORIGIN'
all:
env | grep LDFLAGS
$(MAKE) -f child1.mk
$(MAKE) -f child2.mk
child1.mk:
# add an additional RPATH value
export LDFLAGS += -Wl,-rpath='$$ORIGIN/../..'
all:
env | grep LDFLAGS
child2.mk:
all:
env | grep LDFLAGS
This is what happens:
$ make
env | grep LDFLAGS
LDFLAGS=-Wl,-rpath='$ORIGIN'
make -f child1.mk
make[1]: Entering directory `/build/test'
env | grep LDFLAGS
LDFLAGS=-Wl,-rpath='RIGIN' -Wl,-rpath='$ORIGIN/../..'
make[1]: Leaving directory `/build/test'
make -f child2.mk
make[1]: Entering directory `/build/test'
env | grep LDFLAGS
LDFLAGS=-Wl,-rpath='$ORIGIN'
make[1]: Leaving directory `/build/test'
In the child1 output, 'RIGIN' is bad, the $$ appears to have been evaluated a second time.
The documentation has this note about exports, which could be relevant, but doesn't really explain how to avoid it:
In both of these forms, the arguments to export and unexport are expanded, and so could be variables or functions which expand to a (list of) variable names to be (un)exported.
Using $$$$ORIGIN in the top-level Makefile "works" for child1, but not for child2 which doesn't change the LDFLAGS value and ends up with $$ORIGIN.
This is GNU Make 3.81 under Linux.
The RPATH post I linked to has a workaround, but feels like this shouldn't be necessary:
LDFLAGS="-Wl,-rpath=XORIGIN/../lib" ./configure --prefix=/blabla/place
See the X? That will be replaced by a dollar sign later when you run chrpath on the resultant binaries.

On surface, this seems like a possible unintended bug with make integration with environment variables. It seems that whenever an environment variables is passed picked up by 'make', it will 'expand' any '$v', (and potentially other constructs).
Consider this very simple makefile
all:
echo "V=$V"
env | grep V=
Setting V to '$$SHELL' (bash export V='$$SHELL'), and running make will show that the internal 'V' variable is /bin/bash, while the environment variable is '$$SHELL'
echo "V=$SHELL"
V=/bin/bash
env | grep V=
V=$$SHELL
Putting a side the question if this is a feature or bug, you can use the $(value var) function to access the "raw" value. In the child make use:
# Append extra flags to env LDFLAGS, without 'expansion'
export LDFLAGS := $(value LDFLAGS) -Wl,-rpath='$$ORIGIN/../..'

Here is a working example how to pass $ORIGIN through multiple levels of makefiles with bash:
SHELL := /bin/bash
ifeq (${MAKELEVEL},0)
export LDFLAGS := -Wl,-rpath=`printf "%bORIGIN" "\044"`
endif
all :
#echo MAKELEVEL=${MAKELEVEL} LDFLAGS=${LDFLAGS}
#if [[ ${MAKELEVEL} -lt 3 ]]; then ${MAKE}; fi
Outputs:
$ make --no-print-directory
MAKELEVEL=0 LDFLAGS=-Wl,-rpath=$ORIGIN
MAKELEVEL=1 LDFLAGS=-Wl,-rpath=$ORIGIN
MAKELEVEL=2 LDFLAGS=-Wl,-rpath=$ORIGIN
MAKELEVEL=3 LDFLAGS=-Wl,-rpath=$ORIGIN
Alternatively, if you are on Linux, build without $ORIGIN but then use Exodus for painless relocation of Linux binaries – and all of their dependencies – without containers.

You can also set up additional variable of O to expand to the same before appending to LDFLAGS, so that even when make expands $O in $ORIGIN, it will still result in $ORIGIN:
$ cat child1.mk
# add an additional RPATH value
O := $$O
LDFLAGS += -Wl,-rpath='$$ORIGIN/../..'
all:
env | grep LDFLAGS
Output:
$ make
env | grep LDFLAGS
LDFLAGS=-Wl,-rpath='$ORIGIN'
make -f child1.mk
make[1]: Entering directory '/home/raspy/so-62298900'
env | grep LDFLAGS
LDFLAGS=-Wl,-rpath='$ORIGIN' -Wl,-rpath='$ORIGIN/../..'
make[1]: Leaving directory '/home/raspy/so-62298900'
make -f child2.mk
make[1]: Entering directory '/home/raspy/so-62298900'
env | grep LDFLAGS
LDFLAGS=-Wl,-rpath='$ORIGIN'
make[1]: Leaving directory '/home/raspy/so-62298900'

Related

make[1]: Command not found

I am getting below error while building some set of files. Would someone give some pointers on why do I get this error? Am I missing installation of some package?
Snippet of error log:
make[1]: MMD: Command not found
CC drivers/usb/usbhid.libc.o
make[1]: MMD: Command not found
CC drivers/usb/usbmsc.libc.o
make[1]: MMD: Command not found
CC drivers/hid.libc.o
make[1]: MMD: Command not found
AR build/libc.a
make[1]: invalidar: Command not found
Makefile.inc:89: recipe for target 'build/libc.a' failed
Snippet of my makefile:
# macro to define template macros that are used by use_template macro
define create_cc_template
# $1 obj class
# $2 source suffix (c, S)
# $3 additional compiler flags
# $4 additional dependencies
ifn$(EMPTY)def $(1)-objs_$(2)_template
de$(EMPTY)fine $(1)-objs_$(2)_template
$(obj)/$$(1).$(1).o: $$(1).$(2) $(obj)/libpayload-config.h $(4)
#printf " CC $$$$(subst $$$$(obj)/,,$$$$(#))\n"
$(CC) $(3) -MMD $$$$(CFLAGS) -c -o $$$$# $$$$<
en$(EMPTY)def
end$(EMPTY)if
endef
It looks to me as if the CC variable is not defined to anything when you invoke this macro, and the third argument is empty. This means that the recipe make internalizes after the eval is expanded is:
-MMD $$(CFLAGS)...
A quick fix is to escape the variable for CC:
$$(CC) $(3) -MMD ...
I think your expansion model for this is very odd, and probably incorrect in other ways. But, without seeing how this macro is used it's hard to say.
One easy way to debug eval issues is to duplicate the context where the eval appears and replace the eval with the info function. This will print exactly what make will parse, and it should be completely normal and understandable makefile syntax; e.g., change something like:
$(foreach X,$(STUFF),$(eval $(call FOO,$X)))
to:
$(foreach X,$(STUFF),$(info $(call FOO,$X)))
$(foreach X,$(STUFF),$(eval $(call FOO,$X)))

make: gcc: Command not found with include in makefile

I am trying to a create a makefile architecture so I have one global makefile at the top which call another in a subdirectory.
Global makefile :
CC = gcc
CFLAGS = -W -Wall -pedantic
LDFLAGS =
PROJECT = proj
SOURCES =
PATH = $(PROJECT)
include $(PATH)/Makefile
all : $(PROJECT).exe
$(PROJECT).exe :
$(CC) $(CFLAGS) $(LDFLAGS) $(DEFINES) $(INCLUDES) $(SOURCES) -o $#
clean :
rm -rf *.exe
Subdirectory makefile :
CC = gcc
CFLAGS = -W -Wall -pedantic
LDFLAGS =
SOURCES += $(PATH)/main.c
When I "make" in the top directory I have the following error :
gcc -W -Wall -pedantic proj/main.c -o proj.exe
make: gcc: Command not found
make: *** [Makefile:17: FK16_jake.exe] Error 127
But when i type exactly the same command (gcc -W -Wall -pedantic proj/main.c -o proj.exe) in my terminal, it's building.
And if I remove the makefile include, he recognize gcc but don't build because no sources (normal behavior).
You are overriding PATH, which is used to determine where to look for executables. Change this variable and it should work.
In the UNIX shell, the environment variable PATH specifies the set of directories to search for executables when you run a command.
The GNU Make manual says:
When make runs a recipe, variables defined in the makefile are
placed into the environment of each shell. This allows you to pass
values to sub-make invocations (see Recursive Use of make). By default, only variables that came from the environment
or the command line are passed to recursive invocations.
Because PATH is a shell variable and is defined in the environment of make, it is "came from the environment". That means when you modify the value with PATH = $(PROJECT) the updated value will be set in the environment of the shell that runs the recipe for $(PROJECT).exe. That means the shell runs with a bad value for the PATH and so cannot find gcc.
To avoid this confusion, do not use PATH as a makefile variable. Use another name which is not the same as an environment variable that the shell depends on. You could call it PROJECT_PATH instead, or just use $(PROJECT) instead of $(PATH).

Makefile - export declarations to sub-makefiles

I know variables can be exported to sub-makefiles: Communicating Variables to a Sub-make
Example:
Makefile:
export PWD := $(shell pwd)
target:
#echo $(PWD)
#cd somewhere; $(MAKE)
somewhere/Makefile
target:
#echo $(PWD)
Supposing that the first Makefile is located at /path/to/first/makefile, the code above will print:
/path/to/first/makefile
/path/to/first/makefile
My question is: is there a way to let the variable PWD be implicitly evaluated inside sub-makefiles?
The output should look like this:
/path/to/first/makefile
/path/to/first/makefile/somewhere
So far I can only think of:
Exporting the literal declaration and use the function eval
Do it somehow with .SECONDEXPANSION
Put the declaration into a separate file and include it
in both the first and the second Makefile
All this solution are explicits: they imply code to be added to the sub-makefiles.
What I'm searching is an implicit solution which will change only the code inside the first Makefile.
Plus, honestly...the first two solutions are so ugly I would rather declare manually PWD in every sub-makefile.
[EDIT]
Just to make it more clear: the variable PWD is just an example, I'm not trying to obtain the path of every Makefile.
Use ${MAKEFILE_LIST} variable and let make change directories for you:
[max#earth:~/tmp]$ cat Makefile
target:
#echo $(abspath $(lastword ${MAKEFILE_LIST}))
${MAKE} -C somewhere $#
[max#earth:~/tmp]$ cat somewhere/Makefile
target:
#echo $(abspath $(lastword ${MAKEFILE_LIST}))
[max#earth:~/tmp]$ make target
/home/max/tmp/Makefile
make -C somewhere target
make[1]: Entering directory '/home/max/tmp/somewhere'
/home/max/tmp/somewhere/Makefile
make[1]: Leaving directory '/home/max/tmp/somewhere'
That prints the full path to the makefile being processed. Use $(dir $(abspath $(lastword ${MAKEFILE_LIST}))) to chop off filename Makefile.

How to pass down option -f SomeMakefile to a sub-make? [here with GNUMake]

When invoking itself recursively via some $(MAKE) foo in recipes, GNUMake passes down some of the options it was called with, but not all of them. In particular it does not pass down a -f SomeMakefile option. See section 5.7.3 of manual.
How can I find whether make was invoked with some -f option and how can I pass it down to a sub-make ?
To make the question concrete, here is what my SomeMakefile contains:
%.pdf : %.tex
pdflatex $(PDFLATEXFLAGS) $*
#if [ -f $*.log ] ; then $(MAKE) --silent $*.slw; fi
The problem is that how to make foo.slw is defined in SomeMakefile and the recursive make won't use it.
You can get the name of the makefile from MAKEFILE_LIST variable. E.g.:
${MAKE} -f $(lastword $(MAKEFILE_LIST))
If your makefile includes other makefiles you may like to store the name of the makefile early into an immediately assigned variable, e.g.:
# Somewhere at the top of your makefile, before any includes
this_makefile := $(lastword ${MAKEFILE_LIST})
# and use it later
some_rule :
${MAKE} -f ${this_makefile}
Alternatively, if you know that your makefile is always the first one read by make, then it is going to be in the front of MAKEFILE_LIST, e.g. $(firstword ${MAKEFILE_LIST}).

How to suppress command-line defintions from being exported to a sub-make?

The makefile may be something like the following:
MAKEOVERRIDES=
all:
#$(MAKE) recursive
recursive:
#echo $(foo)
.PHONY: all
.PHONY: recursive
If I run:
$ make foo=bar
I get:
make[1]: Entering directory '/home/myname'
bar
make[1]: Leaving directory '/home/myname'
The idea to suppress command-line definitions is described here:
The command line variable definitions really appear in the variable
'MAKEOVERRIDES', and 'MAKEFLAGS' contains a reference to this variable.
If you do want to pass flags down normally, but don't want to pass down
the command line variable definitions, you can reset 'MAKEOVERRIDES' to
empty, like this:
MAKEOVERRIDES =
To suppress a variable from being exported to sub-make use unexport:
MAKEOVERRIDES=
unexport foo
all:
#$(MAKE) recursive
recursive:
#echo $(foo)
.PHONY: all
.PHONY: recursive

Resources