Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Makefile Use Shell to create variables

I am using make's shell command to populate some variables, and on output it indicates that they are being set to the values I expect. However when I pass them to my make recipes they all show up empty. I suspect make is doing some odd parsing on the results. Eg:

MyTarget: $(MySources)
    LINE='$(shell cat $< | grep GIMME_THE_LINE_I_WANT)'
    CH9=$(shell echo $(LINE) | cut -b9)
    echo $(CH9) # doesn't print anything

I checked my generator commands manually by setting SHELL=sh -XV and when I run identical commands I get the right values, it just looks like bash is 'zeroing' my variables. Any idea what's wrong?

like image 668
LambdaBeta Avatar asked Oct 10 '16 02:10

LambdaBeta


2 Answers

There are several things going on here. The first is that when you have:

MyTarget: $(MySources)
    LINE='$(shell cat $< | grep GIMME_THE_LINE_I_WANT)'

You are setting a shell variable called LINE, not a make variable. The build instructions for a target are all shell commands. So after the first line, the shell variable $LINE contains GIMME_THE_LINE_I_WANT. However...

...each line in the build instructions for a target runs in a separate shell process. So if you have:

mytarget:
    MYVAR=foo
    echo $$MYVAR

You'll see no output, because $MYVAR isn't set in the context of the second command. Also note the use of $$ here, because otherwise the $ would be interpreted by Make (that is, writing $MYVAR would actually be the make expression $M followed by the text YVAR). You can resolve this by logically joining your lines into a single shell script, like this:

mytarget:
    MYVAR=foo; \
    echo $$MYVAR

The \ is Makefile syntax that extends a single logical line over multiple physical lines, and of course ; is simply shell syntax for combining multiple commands on one line.

With all this in mind, we could rewrite your target like this:

MyTarget: $(MySources)
    LINE=$$(cat $< | grep GIMME_THE_LINE_I_WANT); \
    CH9=$$(echo $$LINE | cut -b9); \
    echo $$CH9

Notice that since we are already running a shell script I'm not using Make's $(shell ...) construct, and that I'm making sure to escape all of the $ characters to ensure that the shell, not Make, is handling variable expansion.

Taking it just a little further, you don't need to use cat in that script; you could simply write:

MyTarget: $(MySources)
    LINE=$$(grep GIMME_THE_LINE_I_WANT $<); \
    CH9=$$(echo $$LINE | cut -b9); \
    echo $$CH9

Or:

MyTarget: $(MySources)
    CH9=$$(grep GIMME_THE_LINE_I_WANT $< | cut -b9); \
    echo $$CH9

(NB: While not germane to this solution, it's although worth noting that each invocation of $(shell ...) is also run in a separate process, so a variable set in one won't be available in another.)

like image 191
larsks Avatar answered Sep 28 '22 01:09

larsks


The make runs every command in its separate shell. So, the values are not carried over.

When in doubt, you could always debug it with -d option. Also, a site note, the debug option is very useful when you are trying to figure out why a rule did not fire the way you had intended it.

~> cat Makefile
MyTarget:
        LINE="somevalue"
        echo ${LINE}
~>
~>
~> make -d MyTarget | tail -10
LINE="somevalue"
Putting child 0x2004fbb0 (MyTarget) PID 4052 on the chain.
Live child 0x2004fbb0 (MyTarget) PID 4052
Reaping winning child 0x2004fbb0 PID 4052
echo
Live child 0x2004fbb0 (MyTarget) PID 4192

Reaping winning child 0x2004fbb0 PID 4192
Removing child 0x2004fbb0 PID 4192 from chain.
Successfully remade target file 'MyTarget'.
~>

Just clarifying for the people who downvoted and commented that my above solution doesn't work: First, I apologize for my laziness. May be I should have been clear. Let me try again.

The "make -d" is not a solution to OP's problem.

I tried to show OP how he/she could use debug option to solve a variety of problems that people come across while using makefiles (which, I admit, goes in a slight tangent than just solving the OP's problem at hand).

The above debug shows that the first command was executed in a shell with PID=4052 and the second command was executed in another shell with PID=4192 (which doesn't carry the value of that variable). Also it shows that using a variable with single dollar (${LINE}) just gives you a blank (because the makefile doesn't interpret it as a shell variable).

Again, to be clear: "make -d" is not a solution. Just combine the commands in one line, separated by commas, use double dollars; if the line is long, escape the new lines.

   MyTarget:
      LINE="somevalue"; \
      echo $${LINE}
like image 21
blackpen Avatar answered Sep 28 '22 02:09

blackpen