Get the value from a command in make file - shell

I have a script, which works okay, which I use as follows to get some value:
tmpDir=$(xtr execute run)
in the command code itself I print the value like
fmt.Print("the dir is: ",dir)
and then when I put echo in the script I get the value of tmpDir
Now I need to switch to use make file and I do it exactly like this
and I don't get any value in the tmpDir. What am I missing here?
Here is the makefile
all: app app_2
PROJ_DIR=$(PWD)
# Create folder for build artifacts
DIR=$(xtr execute run)
.PHONY: app
app:
#echo $(DIR)
....

Make recipes are expanded by make first and then passed to the shell. So, your DIR=$(xtr execute run) is first expanded by make but as there is no make variable named xtr execute run, it expands as DIR=.
But it is not your only problem: the variable named DIR in your recipe is a shell variable, not a make variable. It exists only in the context of the shell that make will spawn to execute this part (and only this one) of your recipe:
DIR=$(xtr execute run)
As there is nothing else in this shell invocation, it is useless. It is like you were spawning a new shell, execute DIR= in it, and exit.
The DIR variable in the recipe of app is another variable, with the same name. Again, make expands it before passing the recipe part to the shell but as there is no such make variable, it expands as:
echo
You have a similar problem with $(PWD): it is expanded by make and as there is no such make variable, it expands as the empty string. You could probably use CURDIR, instead, which is a built-in make variable. But remember that PROJ_DIR is a shell variable that will exist only for this single line part of your recipe...
Instead of all this you could try:
DIR := $(shell xtr execute run)
PROJ_DIR := $(CURDIR)
all: app app_2
.PHONY: app
app:
#echo $(DIR)
Here, DIR and PROJ_DIR are make variables that are assigned once (note the :=, it is important) when make parses the makefile. From now on, they can be used in recipes where they will be expanded by make before the part of the recipe is actually passed to the shell for execution.

Related

How to append file path in makefile

I used below sample code in makefile in MacOS to construct path, but it always not works.
all:
ROOT=$(shell pwd)
#echo $(ROOT)
AGENT:=$(ROOT)/agent
#echo $(AGENT)
#mkdir -p $(AGENT)
It will output as AGENT:=/agent, obviously, the AGENT variable is not expanded as ROOT
Several common mistakes here:
Recipe lines run in separate shells, you cannot expect a shell variable assigned in one to be still defined in another.
No need to use the $(shell...) make function in recipes: recipes are already shell scripts.
You apparently mix-up shell variables and make variables.
Shell variables are assigned with var=xxx (no spaces around the =), only in recipes (well, almost only). They are expanded with $var or ${var} but never with $(var), still almost only in recipes.
Make variables are assigned with var := xxx or var = xxx (with or without spaces around := or =), only outside from recipes (well, almost only). They are expanded with $(var) or ${var}, never with $var, anywhere. Note that = and := are different but this is out of scope.
If you are a make beginner please ignore the "almost" in the above explanation, you will not need this for now.
A Makefile is written with two different languages: the shell language in recipes and the make language everywhere else. A bit like a web page is written in HTML and in CSS. The shell language and the make language look sometimes similar but they are different. Something important to consider is that make will pass the recipes to the shell... after having expanded them first to substitute make variables or make function calls. It is thus frequently necessary to protect parts of the shell syntax from this make expansion (see below for an example).
Anyway, try something like this, maybe:
With make variables
ROOT := $(shell pwd)
AGENT := $(ROOT)/agent
all:
#echo $(ROOT)
#echo $(AGENT)
#mkdir -p $(AGENT)
ROOT and AGENT are make variables, assigned outside any recipe with := and available anywhere, including in recipes. They are expanded with $(var). Before passing the recipe to the shell make will expand it as:
echo /some/where
echo /some/where/agent
mkdir -p /some/where/agent
Each line will be executed in a separate shell but it does not matter because there is no shell variables passing between them.
Note: if you are using GNU make, the CURDIR make variable is defined and expands as the current working directory. No need to use pwd, just use $(CURDIR) instead of $(ROOT):
AGENT := $(CURDIR)/agent
all:
#echo $(CURDIR)
#echo $(AGENT)
#mkdir -p $(AGENT)
Note: if ROOT refers to where the Makefile is located and if you invoke make from elsewhere using the -f make option, $(CURDIR) does not work any more because it points to where you invoked make from, not where the Makefile is located. But you can also get the correct ROOT with:
ROOT := $(dir $(lastword $(MAKEFILE_LIST)))
at the very beginning of your Makefile (before any include statement).
With shell variables
Use only shell syntax and a one-line recipe:
all:
#ROOT="`pwd`"; AGENT="$$ROOT"/agent; echo "$$ROOT"; echo "$$AGENT"; mkdir -p "$$AGENT"
ROOT and AGENT are shell variables, assigned with = inside the recipe and available only in the very same line of the recipe. Note the double $ used to expand shell variables. They are needed to escape the first expansion that make performs before passing the result to the shell. After expansion by make the recipe is passed to the shell as:
ROOT="`pwd`"; AGENT="$ROOT"/agent; echo "$ROOT"; echo "$AGENT"; mkdir -p "$AGENT"
(make expands $$ as $ and stops there) which is what you want. It is executed in one single shell, reason why the shell variables are passed from one command of the list to the next.
If you wish, for better readability, you can use the \ line continuation to split the recipe on several lines, but still have only one single recipe executed in one single shell:
all:
#ROOT="`pwd`"; \
AGENT="$$ROOT/agent"; \
echo "$$ROOT"; \
echo "$$AGENT"; \
mkdir -p "$$AGENT"
This is strictly equivalent to the other form, just a bit easier to read. But it is completely different from:
all:
#ROOT="`pwd`"
#AGENT="$$ROOT/agent"
#echo "$$ROOT"
#echo "$$AGENT"
#mkdir -p "$$AGENT"
because with this last form the 5 lines are executed by 5 different shells. When the second one runs, for example, the ROOT shell variable assigned by the first shell is not defined any more and the result is the same as:
AGENT="/agent"
Then, the two echo lines echo nothing at all, just a newline. And finally you get an error because:
mkdir -p
is not valid.

Export environment variables from child bash script in GNU Make

I have a script "set_env.py" that outputs the following uppon execution:
export MY_VAR_1=some_value
export MY_VAR_2=some_other_value
I cannot change this script, it is supplied in my current environment.
Further I have a Makefile that looks like this:
SHELL := /bin/bash
all: set_env
env | grep MY_VAR
set_env:
eval ./set_env.py
With this makefile I expected the grep to list my two variables, however it seems the environment is not set.
I suspect this is because make creates a sub-environment for each line and so the variables set on the first line will not be available in the second.
So the question is, how would I go about exporting the environment from the untouchable script in my makefile?
Actually, the output of the python is valid make.
One option then is to read the output of the python directly into the makefile.
The only fly in the ointment is that $(shell) doesn't cut the mustard.
include Environment.mk
PHONY: test
test:
env | grep MY_VAR
Environment.mk:
./set_env.py >$#-tmp
mv $#-tmp $#
How does this work?
The first thing that make tries to do is to ensure the makefile itself is up-to-date.
Since we have told it to include Environment.mk,
make must ensure that is up-to-date too.
Make finds a rule for Environment.mk
The python is run, creating Environment.mk
Environment.mk is read in, creating two make variables with the export attribute
The makefile is now up-to-date, so make proceeds on to the target (test in this case)
Make runs test's recipe, exporting any variables with the export attribute.
No recursion, but you should ensure the python always spits out make compatible syntax.
EDIT
As #raspy points out, this is not the whole story.
As it stands,
once Environment.mk has been created,
it will never be regenerated.
If set_env.py ever generates different output,
you should tell make what conditions these are by adding dependencies.
If set_env.py takes a trivial time to run,
I advise a simple .PHONY.
That way it will run every time you run make,
and Environment.mk will never be stale.
.PHONY: Environment.mk
Recursive make is your friend I'm afraid.
.PHONY: all
all:
eval $$(./set_env.py) && ${MAKE} test
.PHONY: test
test:
env | grep MY_VAR
There are a few moving parts here.
make all executes the shell command eval $(./set_env.py) && make test
The shell first does the command substitution
$(./set_env.py) is replaced by the export commands
The export commands are passed to the (shell) eval command
The environment variables are defined, but only for this shell invocation
The eval succeeds, so the execution passes to the command after the &&
Make runs recursively, but this second make has an augmented environment

Export variable declared in script.sh and Import the value in mupltiple Makefiles

I am trying to create something like a global variable that I will use in order to make my project easy to deploy for other developers.
I would like to have an .sh file where there is a variable defining the location of the project.
Later on I want to export this variable and make it accessable in every makefile that I am creating so that I can use this design to keep everything constant and in one place.
This is an example of what I am trying to build:
Creating and exporting the variables in script.sh:
#!/bin/bash
DIRECTORY='some path value here'
Importing the values in multiple Makefiles:
# start script and fetch the value
VAR := $(shell ./script.sh | sed -n '/^result: /s/^.*: //p')
all:
#echo VAR=$(VAR)
I would like to see how other people are dealing with the same problem.
Being a better developer is my goal here. :)
Feedback always welcomed.
Environment variables exported in the shell are visible from make, so in a shell script like this:
#!/bin/sh
VAR=value
export VAR
make $*
The Makefile will start with VAR defined to value. That's one way to get variables from a shell script into make.
If you don't want the shell script to run make, you can have a user source it:
$ source script.sh
$ make
The variables set in the script will be visible to make this way too.
Or course there doesn't seem to be any reason you need a shell script here. Stick your configuration into a fragment of a Makefile (which would look almost exactly like your shell script, but not use quotes for multiple word values) and then include Makefile.inc in your main makefile.
Also note that syntax like this:
#!/bin/sh or another commment
VAR=value
export VAR
It equally valid included in a Makefile or sourced into a shell script. So sometimes it's possible to use the same include file in both places!

Invoking bash commands in makefile [duplicate]

This question already has an answer here:
command substitution doesn't work with echo in makefile [duplicate]
(1 answer)
Closed 6 years ago.
Inside of a makefile, I'm trying to check if fileA was modified more recently than fileB. Using a few prior posts (this and this) as references, I've come up with this as an attempt to store the time since last file modification as a variable:
(I'd rather this be in a function outside of a make recipe, but one step at a time.)
.PHONY: all clean
all : (stuff happens here)
radio :
BASE_MOD_TIME="$( expr $(date +%s) - $(date +%s -r src/radio_interface/profile_init.c) )"
#echo "$(BASE_MOD_TIME)"
I thought that I would be assigning the output of the expr command to a variable, BASE_MOD_TIME, but the output is:
bash-4.1$
BASE_MOD_TIME=""
echo ""
What am I doing wrong here? Simple attempts to save the output of ls -l also didn't work like this.
Make variables are normally global, and you don't normally set make variables in a recipe. A recipe is simply a list of commands to be executed by a shell, and so what looks like a variable assignment in a recipe is a shell variable assignment.
However, each line in a make recipe is run in its own shell subprocess. So a variable set in one line won't be visible in another line; they are not persistent. That makes setting shell variables in recipes less useful. [Note 1]
But you can combine multiple lines of a recipe into a single shell command using the backslash escape at the end of the line, and remembering to terminate the individual commands with semicolons (or, better, link them with &&), because the backslash-escaped newline will not be passed to the shell. Also, don't forget to escape the $ characters so they will be passed to the shell, rather than being interpreted by make.
So you could do the following:
radio:
#BASE_MOD_TIME="$$( expr $$(date +%s) - $$(date +%s -r src/radio_interface/profile_init.c) )"; \
echo "$$BASE_MOD_TIME"; \
# More commands in the same subprocess
But that gets quite awkward if there are more than a couple of commands, and a better solution is usually to write a shell script and invoke it from the recipe (although that means that the Makefile is no longer self-contained.)
Gnu make provides two ways to set make variables in a recipe:
1. Target-specific variables.
You can create a target-specific variable (which is not exactly local to the target) by adding a line like:
target: var := value
To set the variable from a shell command, use the shell function:
target: var := $(shell ....)
This variable will be available in the target recipe and all dependencies triggered by the target. Note that a dependency is only evaluated once, so if it could be triggered by a different target, the target-specific variable might or might not be available in the dependency, depending on the order in which make resolves dependencies.
2. Using the eval function
Since the expansion of recipes is always deferred, you can use the eval function inside a recipe to defer the assignment of a make variable. The eval function can be placed pretty well anywhere in a recipe because its value is the empty string. However, evaling a variable assignment makes the variable assignment global; it will be visible throughout the makefile, but its value in other recipes will depend, again, on the order in which make evaluates recipes, which is not necessarily predictable.
For example:
radio:
$(eval var = $(shell ...))
Notes:
You can change this behaviour using the .ONESHELL: pseudo-target, but that will apply to the entire Makefile; there is no way to mark a single recipe as being executed in a single subprocess. Since changing the behaviour can break recipes in unexpected ways, I don't usually recommend this feature.
What's wrong with this?
fileB: fileA
#echo $< was modified more recently than $#
Instead of forcing the makefile to do all of the heavy lifting via some bash commands, I just called a separate bash script. This provided a lot more clarity for a newbie to bash scripting like myself since I didn't have to worry about escaping the current shell being used by make.
Final solution:
.PHONY: all clean
all : (stuff happens here)
radio :
./radio_init_check.sh
$(MKDIR_P) $(OBJDIR)
make $(radio_10)
with radio_init_check.sh being my sew script.

How to handle setting up environment in makefile?

So, to compile my executable, I need to have the library locations set up correctly. The problem is, the setup comes from a bunch of scripts that do the env variable exporting, and what needs to be set up may change (beyond my control) so I need to use those scripts instead of copying their functionality. To compile in regular command line, I need to do something like:
setup library1
setup library2
source some_other_setup_script.bash
g++ blah.c
# setup is a executable on my system that run some scripts
How would I write a makefile that accomplishes that? As far as I tried, the env variable exporting does not carry over (i.e. "export VAR=remember; echo $VAR" won't work)
You can also add environment variables properly with the machinery of GNU make, like so:
export TEST:="Something Good!"
test:
echo $$TEST
This (I think) has different semantics from:
TEST2:="Something not quite so useful?"
test2:
echo ${TEST2}
Which (again, I think) does the substitution within make before passing along to the shell. Note that the export command doesn't work within a target block, just unindented as an immediately executed command.
If variable exporting is not working the way it does on your command line, that suggests that Make is choosing a shell different from the one you're using, with different syntax for handling variables (export VAR=remember; echo $VAR works fine for me). Make uses /bin/sh by default, but you can override this with the SHELL variable, which Make does not import from the environment. I suggest setting SHELL (in the Makefile) to whatever you're using in your environment and trying the export VAR=remember experiment again.
Ultimately you will need to define the variable and execute the compiler in a shell list or even a script, rather than in separate make commands. There are a couple of refinements you could add, however. You could tell make about the script:
maintarget: script.sh blah.c
source script.sh; g++ blah.c
script.sh:
setup include script here
Another thing would be to just execute all that stuff in the same shell
maintarget: blah.c
run this; run that; run the other thing; g++ blah.c
I believe all make versions will run a ; list in the same shell, but you can always force a subshell with (list) or by calling specifically a shell script as a compiler command wrapper.
Don't forget to have the appropriate targets depend on your scripts themselves. BTW, some make versions (pmake aka bsd make) can execute a command when defining a make variable, and all versions of make then exports those. But I don't think gmake can do that.
You could write another shell script that executes all those commands, then prints out variable assignments that make can use. Run the script, pipe its output to a file, then include that file from your Makefile. For example:
Makefile:
all:
echo $(FOO)
test.mk: test.sh
./$< > $#
include test.mk
test.sh
echo "FOO=1"
Running "make" in the directory containing this Makefile produces:
make: Entering directory `/home/luser/build/mktest'
Makefile:7: test.mk: No such file or directory
./test.sh > test.mk
make: Leaving directory `/home/luser/build/mktest'
make: Entering directory `/home/luser/build/mktest'
echo 1
1
make: Leaving directory `/home/luser/build/mktest'
make creates test.mk by running the shell script, then includes it. test.mk contains the output of test.sh, and is parsed as a Makefile. See http://www.gnu.org/software/make/manual/make.html#Include for more details.
We use a variant of this in Mozilla's client.mk to let you define options in a "mozconfig" file:
http://mxr.mozilla.org/mozilla-central/source/client.mk#138
Restatement: How do I get a shell variable into a make file?
Something like:
MYVAR := $(shell echo $(MYVAR)) <any_makefile_additions_here>
So, this defines MYVAR inside a MAKEFILE when an environment variable named MYVAR is also set.
It might be of interest, that, in order to override an option that is already defined in a makefile, make supports (I am referring to GNU Make 3.82, but other version probably too) the option -e.
Example:
Makefile:
CC=gcc
...
Run make:
CC=gcc-4.7
make -e
will use gcc-4.7 instead of gcc.

Resources