0

Ive spent some time reading the GNU make manpages, but cant seem to figure out if what Im trying to achieve is actually possible.

Basically I just want to pass variables into info( or error( - but nothing Ive tried so far seems to work as Id expect

current version of makefile, Ive also tried multiple variations to try and get the variables to present inside of the info & error calls, all with no luck so far.

error_prefix=ERROR:
info_prefix=INFO:

define print_error
  final_error="$(error_prefix) $1 $2"
  echo $(error fe:$(final_error))
  $(error fe:$(final_error))
endef

define print_info
  final_info="$(error_prefix) $1 $2"
  echo $(info fi:$(final_error))
  $(info fi:$(final_error))
endef

.PHONY: test

test:
        response=$$(eval curl -s -o /dev/null -w "%{http_code}" www.google.com); \
        echo $$response; \
        $(call print_info, $(value $response), A); \
        $(call print_error, $(value $response), B);


4
  • 1
    The way you are mixing shell constructs and make constructs suggests that you should probably read at least the introduction of a make manual, e.g. this one. echo $(info...) does not really make sense. If you want to print something when the shell runs a recipe simply echo something. And if you want to see it when make expands the recipe (before passing it to the shell), then simply $(info something). And why do you eval curl ... instead of just curl? Commented Apr 25, 2024 at 12:15
  • 1
    Note also that $(value $response) in a recipe is expanded by make. $response probably becomes esponse because you have no make variable named r. And $(value esponse) probably becomes the empty string because you have no make variable named esponse. Commented Apr 25, 2024 at 12:20
  • Consider also whether what you have in mind is even worth doing. The mere idea of custom error messages suggests to me that what you really want to do is write a script. make is good at what it's intended for, but it is a darn poor choice of scripting language. Commented Apr 25, 2024 at 13:31
  • Finally and separately, when asking questions please show the actual output you received when you tried your solution (using cut and paste with code block formatting), AND ALSO show explicitly what you want to achieve. Questions like this can't really be answered because you haven't explained what you really want to do; we can only infer it from your attempt at a solution which obviously is not working (or you wouldn't be asking). Commented Apr 25, 2024 at 13:43

2 Answers 2

2

Your Makefile is a strange mix of shell and make constructs. If you are new to make you should probably use only shell constructs in recipes, plus maybe make variable expansions ($(VARIABLE)). But let's try to see what's going on when you run make with this.

First, it is important to understand that make expands the recipes before passing them to the shell. And when it expands your recipe it finds $(error ...). So it prints the error message and stops before the shell runs.

So, let's replace this $(error ...) with $(info error ...) to avoid that:

define print_error
  final_error="$(error_prefix) $1 $2"
  echo $(info error fe:$(final_error))
  $(info error fe:$(final_error))
endef

Make parses your Makefile and discovers that it must run the recipes of phony target test. Before passing the recipes to the shell it expands them.

When make expands the 2 first lines of your recipes it just replaces the $$ with $. That's fine. When it expands the 2 last lines it first expands the parameter of value: $response. As you don't have a make variable named r, $r expands as the empty string and the parameter of value becomes esponse. As you don't have a make variable named esponse, $(value esponse) expands as the empty string. You could have written $(call print_info, , A), it would do the same (note the space between the two commas and before A).

When make expands $(call print_info, , A) it replaces it with (leading tabs and spaces not shown):

final_info="$(error_prefix)   A"
echo $(info fi:$(final_error))
$(info fi:$(final_error)); \

Note: there are 3 spaces before A. Do you understand why?

The expansion continues recursively. Let's first expand the make variables error_prefix and final_error. As you don't have a make variable named final_error (you have a shell variable with this name but that's different from a make variable) it expands again as the empty string. This gives:

final_info="ERROR:   A"
echo $(info fi:)
$(info fi:); \

The expansion of info prints the information message and expands as the empty string. So you see:

fi:
fi:

on the standard output and the recipe becomes:

final_info="ERROR:   A"
echo 
; \

That's the end of this expansion. The expansion of the last line similarly becomes:

final_error="ERROR:   B"
echo 
;

and two more lines are printed on the standard output which should now look like:

fi:
fi:
error fe:
error fe:

At this point your recipe is (again, leading tabs and spaces not shown):

response=$(eval curl -s -o /dev/null -w "%{http_code}" www.google.com); \
echo $response; \
final_info="ERROR:   A"
echo
; \
final_error="ERROR:   B"
echo 
;

That's 5 different recipes:

Recipe 1:

response=$(eval curl -s -o /dev/null -w "%{http_code}" www.google.com); \
echo $response; \
final_info="ERROR:   A"

Recipe 2:

echo

Recipe 3:

; \
final_error="ERROR:   B"

Recipe 4:

echo 

Recipe 5:

;

Make first echoes Recipe 1 and your standard output now looks like:

fi:
fi:
error fe:
error fe:
response=$(eval curl -s -o /dev/null -w "%{http_code}" www.google.com); \
echo $response; \
  final_info="ERROR:    A"

Then make passes Recipe 1 to the shell that runs eval curl ... (why eval?), stores the output 200 in shell variable response, echoes it, and stores ERROR: A in shell variable final_info. Your standard output is now:

fi:
fi:
error fe:
error fe:
response=$(eval curl -s -o /dev/null -w "%{http_code}" www.google.com); \
echo $response; \
  final_info="ERROR:    A"
200

Note that shell variables response and final_info are local to this recipe execution; make executes the different recipes in different shell processes. So after Recipe 1 has been executed the values of shell variables response and final_info is definitely lost. In case you expected them to be available when executing the next recipes, forget about it, they are gone.

Make then echoes Recipe 2 and passes it to the shell that simply adds a newline:

fi:
fi:
error fe:
error fe:
response=$(eval curl -s -o /dev/null -w "%{http_code}" www.google.com); \
echo $response; \
  final_info="ERROR:    A"
200
echo

It finally echoes Recipe 3:

fi:
fi:
error fe:
error fe:
response=$(eval curl -s -o /dev/null -w "%{http_code}" www.google.com); \
echo $response; \
  final_info="ERROR:    A"
200
echo

; \
  final_error="ERROR:    B"

When it passes Recipe 3 to the shell there is an error:

/bin/sh: -c: line 0: syntax error near unexpected token `;'
/bin/sh: -c: line 0: `; \'
make: *** [Makefile:19: test] Error 2

because you cannot have a semi-colon like this at the beginning of a list of shell commands. Make stops and Recipe 4 and Recipe 5 are not echoed or executed.

Sign up to request clarification or add additional context in comments.

Comments

1

...cant seem to figure out if what Im trying to achieve is actually possible.

It's not, because:

After a Makefile has been read, when make has determined that a recipe is to be executed for a target, make first performs complete make-expansion of the the recipe, then invokes a shell to run each shell-line of whatever remains.

See this:

$ cat Makefile 
.PHONY: test

test:
    echo Making test; \
    $(info *Prints* this to stdout immediately when expanded and...)
    $(info ...*expands* to nothing before test is run)


$ make
*Prints* this to stdout immediately when expanded and...
...*expands* to nothing before test is run
echo Making test; \

Making test

This:

$ cat Makefile

.PHONY: test

test:
    echo Making test; \
    $(error Prints this to stderr immediately when expanded and Make exits failure before test is run)


$ make
Makefile:5: *** Prints this to stderr immediately when expanded and Make exits failure before test is run. Stop.

And this:

$ cat Makefile 
SHELL:=/bin/bash

.PHONY: test

FOO = $bar

test:
    @echo ============================================= ;\
    echo Making test; \
    echo [$(info #1 FOO == $(FOO): Wrong! )]; \
    echo [$(info #1 FOO == $(value $FOO): Wrong! )]; \
    echo [$(info #2 FOO == $(value FOO): Right! )]; \
    I_am_a_shell_variable="Make does not know about me"; \
    echo [$(info #3 I_am_shell_variable == "$(value I_am_a_shell__variable)")]; \
    I_am_a_shell_variable+=" because Make has already been and gone"; \
    echo [$(info #4 I_am_a_shell_variable == "$(value I_am_a_shell__variable)")]; \
    echo $$I_am_a_shell_variable; \
    echo test made.


$ make
#1 FOO == ar: Wrong! 
#1 FOO == : Wrong! 
#2 FOO == $bar: Right! 
#3 I_am_shell_variable == ""
#4 I_am_a_shell_variable == ""
=============================================
Making test
[]
[]
[]
[]
[]
Make does not know about me because Make has already been and gone
test made.

Don't invoke any make-expansions in a recipe unless it makes sense for them to be expanded before the recipe is run. Things that you cannot accomplish that way are to be accomplished in the shell (in which all applicable tools are at your disposal).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.