Defining recursively expanded variable with same name as environment variable - makefile

I'm trying to lazily evaluate configuration option. I want to issue a Make error only if the variable is actually used (substituted).
Consider the following Makefile:
VAR = $(error "E")
NFS_DIR = NFS_DIR is $(VAR)
T = $(NFS_DIR) is 1
all:
echo Test
If I run it with my environment (which has /srv/nfs value), the output is following:
➜ ~ make
echo Test
Makefile:3: *** "E". Stop.
So the recursive definition acts like simple definition.
If I clear the environment, it works as expected:
➜ ~ env -i make
echo Test
Test
I couldn't find any mention that recursively-expanded variable, when defined with same name as environment variable, will act like simply-expanded variable.
So the questions are:
Why the observed behavior?
How to implement the desired behavior?
Update: to clarify — I don't want to use ?= since the options are configuration options I want them to come strictly from Makefile and not from environment.

Any variable which is in the environment when make starts, will be exported to the environment of any command make runs (as part of a recipe, etc.) In order for make to send the value of the variable to the command, make first has to expand it. It's not acting like a simply-expanded variable. It's just that running a command forces the expansion.
This is kind of a nasty side-effect but I'm not sure there's anything that can be done directly: this behavior is traditional and mandated by POSIX and lots of makefiles would break if it were changed.
You have two choices I can think of. The first is to use the unexport make command to tell make to not send that variable in the command's environment.
The second is to change the name of the variable in make to something that is not a valid environment variable: make will only export variables whose names are legal shell variables (contain only alphanumeric plus _). So using a name like VAR-local instead of VAR would do it.

The question appear to be extremely clear in the title but the actual request get lost in details so the only other reply left it mostly unanswered. Directly answering to the question in the title, which is very interesting, to define a variable in a Makefile with same name as environment variable you can get its value with printenv:
PATH=${shell printenv PATH}:/opt/bin
echo:
echo $(PATH)
Other techniques to achieve the same result without relying on evaluation with external commands are welcome.

Related

Unset is not useful? (Korn shell)

I'm reading this:
You can delete a variable with the command unset varname. Normally this is not useful, since all variables that don't exist are assumed to be null, i.e., equal to empty string "". But if you use the option nounset which causes the shell to indicate an error when it encounters an undefined variable, then you may be interested in unset.
My first question is: I cannot see why the use of unset be not useful; if I want to put my variable to null I can use it (or set variable="" or variable=). On the other hand, if I have a variable that doesn't exist, I don't know why I should have to use it..
My second question is: Why may I be interested in unset in that case?
There is a relevant difference between unset and empty variables.
When you can't tell in front which variables will be used, you can process the output of set (examples: https://stackoverflow.com/a/43419722/3220113 and https://stackoverflow.com/a/28104421/3220113 ).
You might have a situaton where you have sourced a read-only config file, but you do not want all lines set in your environment. In that case you might want to unset the settings you do not need.
When you write some utility that uses some variables, you do not want to leave garbage in the environment. Next to using local variables using unset is another possibility.
I think I have found the answer to my question.
1) If you need to remove the definition and the content of a variable you can use unset command. However, unless you turn on the nounset set option, Korn Shell will allow using variables which don't exist, and it will default the content of such a variable as an empty string. That's why you normally don't use unset: because you normally leave the nounset option off and test variables via conditional logic. Hence in these cases, i.e. the inhibition of the use of a variable, it is not useful. (Obviously, it remains useful for deleting variables - as noted by #Walter A, i.e. "" is not unset, the complete removal of the variable.)
2) That said, it follows that if you use the nounset, unset command makes sense. Indeed, if you unset a variable, the shell will disallow using it.

Makefile 'for' variable as dynamic variable name

I have some dynamically named variables in Makefile. That all works and is fine.
Now I need to reference some of those variables in a for loop, but I can't seem to find the right syntax:
#for module in $(MODULES); do \
(echo $(SRC_$$module)); \
done
each $(MODULE) is a name that needs to be appended to $(SRC_.. for example $(SRC_foo) $(SRC_bar)... but I can't seem to do that with this syntax.
You have expansion-time issues. You can't expand make-level variables dynamically once the shell has started to run. Which is what you are trying to do here.
You either expand the variables at make time or at shell time but not both.
To expand everything at make time you can use something like this.
#$(foreach module,$(MODULES),$(foreach cfile,$(SRC_$(module)),echo '[$(cfile)]';))
Expanding at shell time is possible but a bit messier/trickier.

Use 'subst' in a multiline makefile bash script?

I read this question: Makefile: $subst in dependency list, but I still can't make my shell script work correctly.
I have a makefile with a line with the contents:
##public_detailed#|test_create|Syntax: commoncmdsyntax test_create test_name=<test-name>
A target runs a multiline bash script, where the commoncmdsyntax must be replaced by a string containing words and spaces.
In the script, I use cut to assign to a variable desc the following string:
Syntax: commoncmdsyntax test_create test_name=<test-name>
The problem is that commoncmdsyntax is not replaced by new text here:
$(subst commoncmdsyntax,new text,$$desc)
I also tried to replace it by a single word, like XX, but it also does not work.
The subst function (as in $(subst commoncmdsyntax,new text,$$desc)) is a Make function, so Make will perform the substitution before running any rule and therefore before your script assigns a value to desc. So even if secondary expansion worked the way you seem to think it will, this approach would still fail.
If you want to perform a substitution within something made by a shell script (in a recipe), the sensible way is to do so within the recipe:
echo $dest | sed 's/commoncmdsyntax/new text/'
We can give you a more detailed solution if you give us a minimal complete example of the problem.

Makefile environment variable changes between recipes

I'm attempting to create a single log file that tracks and reports the results of each recipe as the make process moves through the makefile.
To do this, I'm creating an environment variable to hold my logfile reference like so:
LOGDIR = logs
LOGFILE = $(LOGDIR)/$(shell date --iso=seconds).log
The intention is then to add relevant messaging to the log file by using
echo "message" >> $(LOGFILE)
Trouble is, when processing moves from one recipe to the next, the environment variable is re-evaluated, resulting in a new log file from every recipe in my makefile.
Why does the environment variable get re-evaluated, and how can I prevent this to use a single global reference to a logfile?
I thought that using the $(shell operator) syntax prevents re-evaluation of the variable, based on Aaron's answer here.
If you are using GNU make then writing
LOGFILE := $(LOGDIR)/$(shell date --iso=seconds).log
is supposed to evaluate the expression only once. Does that solves your problem?

Shell Scripting: Using a variable to define a path

My problem lies with my confusion with shell variables.
To my understanding, variables allow me to store a value (String in this case) and to call it later in my code. So if I wanted to have a variable that holds the path to some set of scripts, I could ideally just store it like this:
SPTH = '/home/Foo/Documents/Programs/ShellScripts/Butler'
//Later on in the script//
cd $SPTH
./script1
What I'm trying to do, with probably the wrong syntax, is to set the path to variable SPTH.
Then I use cd with argument $SPTH.
Ideally this would allow me to run the file there without typing in the path. However it doesn't work. The $SPTH is ignored and the result is as if cd was used alone.
So what am I doing wrong? And what would be a way to do this?
Don't use spaces...
(Incorrect)
SPTH = '/home/Foo/Documents/Programs/ShellScripts/Butler'
(Correct)
SPTH='/home/Foo/Documents/Programs/ShellScripts/Butler'
To add to the above correct answer :-
For my case in shell, this code worked (working on sqoop)
ROOT_PATH="path/to/the/folder"
--options-file $ROOT_PATH/query.txt

Resources