Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GNU Make - Set MAKEFILE variable from shell command output within a rule/target

I'm trying to put together some complicated makefile rules to automate building a project against multiple compilers. I have one rule that creates some dynamically generated variables and assigns variables to them, then passes those variables along to a call to build a separate rule.

.PHONY: all
all:
    @echo "Detected CPULIST:${CPULIST_DETECTED}"
    @echo "CPULIST:${CPULIST}"
    @for cpu in $(CPULIST_DETECTED); do                   \
        echo "CPU:$${cpu}:";                                \
        eval VARIANTLIST_DETECTED=$(shell 2>&1 find ./.build/linux/$$cpu -mindepth 1 -maxdepth 1 | grep -v '\.svn' |  grep -v warning ); \
        eval echo "Detected Variant List:$${VARIANTLIST_DETECTED}"; \
        eval variant_$${cpu}=$${cpu};                      \
        eval echo "variant_\$${cpu}:\$${variant_$${cpu}}"; \
        $(MAKE) build CPULIST=$${cpu};                     \
    done

.PHONY: build
build: sanity_check $(TARGET)
    @true

I'm having two issues. The first is that, despite double-escaping cpu via $$cpu, it translates to null in the line:

eval VARIANTLIST_DETECTED=$(shell 2>&1 find ./.build/linux/$$cpu -mindepth 1 -maxdepth 1 | grep -v '\.svn' |  grep -v warning ); \

So, the find command searches against ./.build/linux/ for each iteration of the loop rather than looping through ./.build/linux/arm and ./.build/linux/x86 like I would expect. I've included a post where this is described in the references below. I suspect this might be causing a problem because I'm attempting this within a rule itself rather than in the global assignment portion of the makefile (before the rules).

The other problem is occurring at the exact same line. It seems that the shell command is evaluated, assigned to VARIANTLIST_DETECTED, but then VARIANTLIST_DETECTED is executed as if it were a command, since I get the following error during build:

Detected CPULIST:arm x86
CPULIST:
CPU:arm:
/bin/sh: 3: ./.build/linux/x86: Permission denied
Detected Variant List:
variant_arm:arm

It's attempting to run the first result in my query as if it were a command. That line should also be something like ./.build/linux/x86/so.

How do I go about resolving these two issues? They are the last two thorns impeding completion of my makefiles.

Thanks!

References

  1. Assign a makefile variable value to a bash command result?, Accessed 2014-06-19, <https://stackoverflow.com/questions/2373081/assign-a-makefile-variable-value-to-a-bash-command-result>
like image 796
Cloud Avatar asked Feb 12 '23 16:02

Cloud


2 Answers

You need to step back a little bit and think about what's going on here: you seem to just be throwing things into your makefile and hoping the result is what you want, rather than really understanding what's happening and proceeding with a purpose to solve your problem.

The first thing to understand is how make invokes a recipe: first, make expands the recipe. Expanding means make looks through the text of the recipe and finds anything that begins with a $ and treats it like a make function or variable, and evaluates it. So, $$ is converted into a single $, $(CPU_LIST_DETECTED) is expanded to the value of that variable, and $(shell ....) is a make function which is run and the results are included in the recipe text. Maybe you can already see where your recipe is going wrong...

Then second, AFTER all of the recipe has been expanded, make takes the expanded text of the recipe and passes it to the shell. Make then waits for the shell to complete, and make looks at the exit code of the shell. If it's 0, then the recipe succeeds and make continues. If it's not 0, then the recipe fails and make exits with an error. There is NO INTERACTION between make and the shell except make starts it with a script to run, and gets back the exit code. If you couldn't before, maybe you can now see where your recipe is going wrong.

It's virtually always an error (or at least unnecessary) to use a $(shell ...) function inside a recipe... the recipe is already running in the shell! You don't need make to run another shell first, then give those results to a new shell.

Your shell invocation is trying to use a shell variable, $cpu. But that variable has no value here, when make runs the $(shell ...) function, because make runs that function before it starts your shell script. You can't have things in your make functions that refer to things that won't be defined until the recipe is running. This will run the shell script 2>&1 find ./.build/linux/$cpu -mindepth 1 -maxdepth 1 | grep -v '\.svn' | grep -v warning, but the shell variable cpu is not set so the find will recurse through everything under ./.build/linux/.

Second, you are misusing eval here. The shell function expands to a list of words, say foo bar baz. Then the eval will look like this:

eval VARIANTLIST_DETECTED=foo bar baz

which is the same as writing just this:

VARIANTLIST_DETECTED=foo bar baz

which is the same as running the command bar with an argument baz, after setting the variable VARIANTLIST_DETECTED to the value foo. Which is why it seems to be "running your directory".

You probably wanted this:

VARIANTLIST_DETECTED="foo bar baz"

(note quotes). However, getting quotes through an eval is tricky" you have to escape them. Luckily you don't need eval at all here anyway, because you're not trying to assign a dynamically named variable. You can just write the assignment directly.

So, losing the shell function and the eval, that line should be written:

VARIANTLIST_DETECTED=`2>&1 find ./.build/linux/$$cpu -mindepth 1 -maxdepth 1 | grep -v '\.svn' |  grep -v warning`; \

Ditto for the echo: you don't need the eval there.

However, I really don't see the point of setting this variable or the variant_$cpu variable either. You never do anything with them.

like image 79
MadScientist Avatar answered Feb 15 '23 06:02

MadScientist


In general, try to avoid putting Make syntax inside a command. Try this:

VARIANTLIST_DETECTED=$$(find ./.build/linux/$${cpu} -mindepth 1 -maxdepth 1 | grep -v '.svn' |  grep -v warning );
like image 34
Beta Avatar answered Feb 15 '23 06:02

Beta