I’m trying to adapt the Example from the Official Documentation of GNU make to my Use Case:
GNU make – Example of a Conditional
libs_for_gcc = -lgnu
normal_libs =
ifeq ($(CC),gcc)
libs=$(libs_for_gcc)
else
libs=$(normal_libs)
endif
foo: $(objects)
$(CC) -o foo $(objects) $(libs)
So, I created this Prototype:
libs_for_gcc="gcc libs"
normal_libs="normal libs"
libs=
all: do_nothing
@echo "all: done."
do_nothing:
@echo "do_nothing: done."
example:
@echo "example: go ..."
@echo "example - cc: '$(CC)'"
@echo "libs_for_gcc: $(libs_for_gcc)"
@echo "normal_libs: $(normal_libs)"
@echo "libs: $(libs)"
ifeq ($(CC),gcc)
libs=$(libs_for_gcc) && echo "libs: '$$libs'"
@echo "example - libs: '$(libs)'"
else
libs=$(normal_libs) && echo "libs: '$$libs'"
@echo example - libs: $(libs)
endif
@echo "example - libs: '$(libs)'"
@echo "example: done."
test: libs += " -Ddebug"
test: example foo
bar:
@echo "bar: go ..."
ifeq (${libs}, "")
@echo "bar - libs: empty"
@echo "assigning libs"
libs=$(libs_for_gcc)
else
@echo "bar - libs: not empty"
@echo "bar - libs: '${libs}'"
endif
@echo "bar - libs: '${libs}'"
@echo "bar: done."
foo:
@echo "foo: go ..."
ifneq ("$(libs)", "")
@echo "foo - libs: not empty"
@echo "foo - libs: '$(libs)'"
else
@echo "foo - libs: empty"
endif
@echo "foo: done."
Now when I run the Default Target with $ make
it just produces:
$ make
example: go ...
example - cc: 'cc'
libs_for_gcc: gcc libs
normal_libs: normal libs
libs:
libs="normal libs"
example - libs:
example - libs: ''
example: done.
I see that the value of libs
was not changed as intended.
When I run the make bar
Target it produces:
$ make bar
bar: go ...
bar - libs: not empty
bar - libs: ''
bar - libs: ''
bar: done.
Here libs
is not empty but it has nothing inside.
And when I run the make foo
target it produces:
$ make foo
foo: go ...
foo - libs: empty
foo: done.
Here libs
is understood as empty
As I see that libs
is not changed correctly I tried to change the syntax to:
example:
# [...]
ifeq ($(CC),gcc)
libs := $(libs_for_gcc)
@echo "example - libs: '$(libs)'"
else
libs := $(normal_libs)
@echo example - libs: $(libs)
endif
But then I get the GNU make Error:
$ make
example: go ...
example - cc: 'cc'
libs_for_gcc: gcc libs
normal_libs: normal libs
libs:
libs := "normal libs"
/bin/sh: 1: libs: not found
Makefile:7: recipe for target 'example' failed
make: *** [example] Error 127
I couldn’t find any documentation about this behaviour so I appreciate any advise.
Edit:
- Added
all
andtest
Targets.
Background:
The GNU make Command is an important part of the toolchain for packaging and deploying software and thus important in the daily work of System Administrators and DevOps Engineers.
- The Debian and RPM Packaging uses GNU make to package software.
Makefile Driven Packaging
It runs the./configure && make && make install
command sequence. - The Travis CI Workflow uses GNU make for running testsuites.
C Language Automated Testing
It runs the./configure && make && make test
sequence.
All the completely different Use Case are managed by the same Makefile.
Now for my concrete Use Case I’m working on setting up the Travis CI Workflow Sequence to enable Automated Testing for my static linked Source Code Library.
So, contrary to the Packaging Sequence the Automated Testing Sequence requires Debug Features and advanced Output Evaluation to produce a meaningful Test.
I want the Test to check the Error Report and also the Memory Usage Report to alert me of any hidden errors.
Using the Advice about setting the Variable at the Target Declaration Line I was able to change libs
for test
, example
and foo
targets.
I also saw the important hint about the Bash Variable libs
which is only valid on the same line:
$ make
do_nothing: done.
all: done.
$ make test
example: go ...
example - cc: 'cc'
libs_for_gcc: gcc libs
normal_libs: normal libs
libs: -Ddebug
libs="normal libs" && echo "libs: '$libs'" ;
libs: 'normal libs'
example - libs: -Ddebug
example - libs: ' -Ddebug'
example: done.
foo: go ...
foo - libs: empty
foo - libs: ' -Ddebug'
foo: done.
The Recipe libs=$(libs_for_gcc) && echo "libs: '$$libs'"
shows that a new Bash Variable libs
was created and it did not affect the GNU make Variable.
Still the Conditional ifneq ($(libs),)
cannot detect that libs
was already set for the test
target.
2
Answers
Finally I achieved to get my Makefile
make test
Target working.make test
Test Suite with GNU makeThe key was to understand that there are 3 different contexts where a GNU make Variable can be assigned.
And that GNU make often needs helper functions because of some strange issues with Conditionals
These issues are mostly undocumented and only can be solved by Try and Error and searching on StackOverflow.com
1. The first, easiest and best documented context to assign a Variable is in Global Context as seen in the official documentation and the first answer.
GNU make - Example of a Conditional
The Global Variable
${libs}
has global visibility.2. The next context which is not that obvious but still documented in the Official Documentation is in the Target Definition Line
GNU make - Target-specific Variable Values
Here
${libs}
is valid for thetest
Target and all dependent Targets that it calls.But the only Condition is the Target itself.
3. To assign dynamically a Variable in the context of a Target is to use the
$(eval )
Function in combination with the$(shell )
Function as seen in this StackOverflow.com Post:Assign a Variable as Result of a Command
Here
${error}
is read from a File. Additionally the$(strip )
Function is required to be able to check if it is empty, which is some of the undocumented issues that GNU make has and are wierd to Bash Developers.4. Another method that works but does not use a Makefile Variable and is somewhat bulky is evaluating the Variable entirely in Bash in 1 single Line which can be found at: Check Variable with Bash Command Recipe and was also hinted by the previous Answer.
Which would look like:
Here
$$leak
is a Bash Variable and only valid within the same Line. An approach that is somewhat similar to the GitHub Workflow Command Logic. (Actually is was directly ported from the GitHub Workflow for the same Project)As about Evaluation of Conditionals there are many undocumented issues in GNU make that require Bash Workarounds to achieve the goal.
As documented at:
Preprocessing Numerical Values for the Makefile Evaluation
There are issues with comparing Numbers and it is impossible to compare against
0
which is extremely important Exit Code for Command Line Applications. So the Workaround looked somewhat like:In this use case
${error_code}
is tested with a Bash Conditional to populate the Helper Variable${is_error}
withtrue
orfalse
which then can be checked in GNU make.Discussion:
The
test
Target cannot just exit on error.For troubleshooting a failed Automated Test it is crucial to see the Exit Code, the Error Message and the Heap Report.
The difference between your example and the one in the GNU make manual is that in the GNU make manual they are setting a make variable named
libs
(they are assigning the variable outside of any recipe).In your usage you are assigning a shell variable named
libs
(you are assigning it inside the recipe, indented with a TAB).That’s why you get an error when you try to use
:=
, because that’s a make assignment and is not a valid shell assignment.Then in your
echo
you print the make variable$(libs)
which has not been set at all. Further, every logical line of the recipe is run in inside it’s own shell. So you are running the equivalent of:so even if you DID change your echo command to print the shell variable (via
$$libs
) it would be empty because the previous shell, where it was set, has exited.You want to use the same syntax as in the example: take the assignment of the variable OUT of the recipe and set the make variable instead: