Tuesday, January 30, 2024

[SOLVED] Makefile Helper Function Not Expanding As Expected When Called

Issue

Problem Statement

I have a makefile that I'm attempting to make a helper function for. I want to be able to call the defined helper function but when I'm looking at the text expansion for the targets they're coming out as an empty string.

I'm looking to figure out where I'm messing up my Makefile syntax to cause the empty expansion.

Makefile

# Helper function to instantiate templates.
define render_template
$(eval $@_HOST_URI = "[email protected]:company-name")
$(eval $@_TEMPLATE_NAME = $(1))
$(eval $@_TEMPLATE_DIR = $(1)s)
$(eval $@_NAME = $(2))
cd ${$@_TEMPLATE_DIR}; cookiecutter --no-input [email protected]:company-name/terraform-${$@_TEMPLATE_NAME}.git ${$@_TEMPLATE_NAME}_name=${$@_NAME};
endef

# TODO: Add steps to build a Python3 venv and use it to build the local template.
.PHONY: new-service
new-service: ## Accesses Docker Shell (advanced)
        if [ -z "$(SERVICE_NAME)" ]; then \
            echo "SERVICE_NAME is a required parameter. Example usage 'make new SERVICE_NAME=vpc-networking'"; \
        else \
            $(call render_template,"service",${SERVICE_NAME}) \
        fi

Error Output

% make new-service SERVICE_NAME=example
if [ -z "example" ]; then \
            echo "SERVICE_NAME is a required parameter. Example usage 'make new SERVICE_NAME=vpc-networking'"; \
        else \
             \
        fi
/bin/bash: -c: line 0: syntax error near unexpected token `fi'
/bin/bash: -c: line 0: `if [ -z "example" ]; then     echo "SERVICE_NAME is a required parameter. Example usage 'make new SERVICE_NAME=vpc-networking'"; else      fi'
make: *** [new-service] Error 2

Solution

IMO it's a very bad idea to do things like this. But I will explain what's happening so you understand it.

When a define is created, all the newlines are preserved inside the variable; that's what makes a define different from a normal variable assignment where backslash-newlines are removed.

When a variable is expanded in a recipe context, all the newlines are preserved just as if you'd typed them by hand. That's what allows you to put a complete recipe with multiple commands inside a define variable then use it in a recipe and it expands to multiple commands.

In your situation, the eval functions expand to the empty string after assigning the variables, so the expanded value of the call function will be something like:

define render_template




cd "service"s; cookiecutter --no-input [email protected]:company-name/terraform-"service".git "service"_name=;
endef

The newlines here put the cd on its own line, not inside the same if-statement. So make will run it AFTER the if-statement, but since the if-statement fails it never gets the chance.

If you add backslashes to ensure the entire define is on line line, like this:

define render_template
$(eval $@_HOST_URI = "[email protected]:company-name") \
$(eval $@_TEMPLATE_NAME = $(1)) \
$(eval $@_TEMPLATE_DIR = $(1)s) \
$(eval $@_NAME = $(2)) \
cd ${$@_TEMPLATE_DIR}; cookiecutter --no-input [email protected]:company-name/terraform-${$@_TEMPLATE_NAME}.git ${$@_TEMPLATE_NAME}_name=${$@_NAME};
endef

then it will work. Or you could use simple variable assignment:

render_template = \
    $(eval $@_HOST_URI = "[email protected]:company-name") \
    $(eval $@_TEMPLATE_NAME = $(1)) \
    $(eval $@_TEMPLATE_DIR = $(1)s) \
    $(eval $@_NAME = $(2)) \
    cd ${$@_TEMPLATE_DIR}; cookiecutter --no-input [email protected]:company-name/terraform-${$@_TEMPLATE_NAME}.git ${$@_TEMPLATE_NAME}_name=${$@_NAME};


Answered By - MadScientist
Answer Checked By - David Marino (WPSolving Volunteer)