overexchange
overexchange

Reputation: 1

Makefile:108: *** recipe commences before first target

GNU Make 4.1 Built for x86_64-pc-linux-gnu

Below is the Makefile:

# Project variables
PROJECT_NAME ?= todobackend
ORG_NAME ?= shamdockerhub
REPO_NAME ?= todobackend

# File names
DEV_COMPOSE_FILE := docker/dev/docker-compose.yml
REL_COMPOSE_FILE := docker/release/docker-compose.yml

# Docker compose project names
REL_PROJECT := $(PROJECT_NAME)$(BUILD_ID)
DEV_PROJECT := $(REL_PROJECT)dev

# Check and inspect logic
INSPECT := $$(docker-compose -p $$1 -f $$2 ps -q $$3 | xargs -I ARGS docker inspect -f "{{ .State.ExitCode }}" ARGS)

CHECK := @bash -c '\
    if [[ $(INSPECT) -ne 0 ]]; \
    then exit $(INSPECT); fi' VALUE

# Use these settings to specify a custom Docker registry
DOCKER_REGISTRY ?= docker.io

APP_SERVICE_NAME := app

.PHONY: test build release clean tag

test: # Run unit & integration test cases
    ${INFO} "Pulling latest images..."
    @ docker-compose -p $(DEV_PROJECT) -f $(DEV_COMPOSE_FILE) pull
    ${INFO} "Building images..."
    @ docker-compose -p $(DEV_PROJECT) -f $(DEV_COMPOSE_FILE) build cache
    @ docker-compose -p $(DEV_PROJECT) -f $(DEV_COMPOSE_FILE) build --pull test
    ${INFO} "Ensuring database is ready..."
    @ docker-compose -p $(DEV_PROJECT) -f $(DEV_COMPOSE_FILE) run --rm agent
    ${INFO} "Running tests..."
    @ docker-compose -p $(DEV_PROJECT) -f $(DEV_COMPOSE_FILE) up test
    @ docker cp $$(docker-compose -p $(DEV_PROJECT) -f $(DEV_COMPOSE_FILE) ps -q test):/reports/. reports
    ${CHECK} ${DEV_PROJECT} ${DEV_COMPOSE_FILE} test
    ${INFO} "Testing complete"

build: # Create deployable artifact and copy to ../target folder
    ${INFO} "Creating builder image..."
    @ docker-compose -p $(DEV_PROJECT) -f $(DEV_COMPOSE_FILE) build builder
    ${INFO} "Building application artifacts..."
    @ docker-compose -p $(DEV_PROJECT) -f $(DEV_COMPOSE_FILE) up builder
    ${CHECK} ${DEV_PROJECT} ${DEV_COMPOSE_FILE} builder
    ${INFO} "Copying artifacts to target folder..."
    @ docker cp $$(docker-compose -p $(DEV_PROJECT) -f $(DEV_COMPOSE_FILE) ps -q builder):/wheelhouse/. target
    ${INFO} "Build complete"

release: # Creates release environment, bootstrap the environment  
    ${INFO} "Building images..."
    @ docker-compose -p $(REL_PROJECT) -f $(REL_COMPOSE_FILE) build webroot
    @ docker-compose -p $(REL_PROJECT) -f $(REL_COMPOSE_FILE) build app
    ${INFO} "Ensuring database is ready..."
    @ docker-compose -p $(REL_PROJECT) -f $(REL_COMPOSE_FILE) run --rm agent
    ${INFO} "Collecting static files..."
    @ docker-compose -p $(REL_PROJECT) -f $(REL_COMPOSE_FILE) run --rm app manage.py collectstatic --noinput
    ${INFO} "Running database migrations..."
    @ docker-compose -p $(REL_PROJECT) -f $(REL_COMPOSE_FILE) run --rm app manage.py migrate --noinput
    ${INFO} "Pull external image and build..."
    @ docker-compose -p $(REL_PROJECT) -f $(REL_COMPOSE_FILE) build --pull nginx
    @ docker-compose -p $(REL_PROJECT) -f $(REL_COMPOSE_FILE) pull test
    ${INFO} "Running acceptance tests..."
    @ docker-compose -p $(REL_PROJECT) -f $(REL_COMPOSE_FILE) up test
    ${CHECK} $(REL_PROJECT) $(REL_COMPOSE_FILE) test
    @ docker cp $$(docker-compose -p $(REL_PROJECT) -f $(REL_COMPOSE_FILE) ps -q test):/reports/. reports
    ${INFO} "Acceptance testing complete"

clean:
    ${INFO} "Destroying development environment..."
    @ docker-compose -p $(DEV_PROJECT) -f $(DEV_COMPOSE_FILE) kill
    @ docker-compose -p $(DEV_PROJECT) -f $(DEV_COMPOSE_FILE) rm -f -v
    @ docker-compose -p $(REL_PROJECT) -f $(REL_COMPOSE_FILE) kill
    @ docker-compose -p $(REL_PROJECT) -f $(REL_COMPOSE_FILE) rm -f -v
    @ docker images -q -f dangling=true -f label=application=$(REPO_NAME) | xargs -I ARGS docker rmi -f ARGS
    ${INFO} "Clean complete"

tag:
    $(INFO) "Tagging release image with tags $(TAG_ARGS)"
    @ $(foreach tag, $(TAG_ARGS), docker tag $(IMAGE_ID) $(DOCKER_REGISTRY)/$(ORG_NAME)/$(REPO_NAME):$(tag);)
    ${INFO} "Tagging complete"


# Cosmetics 
YELLOW := "\e[1;33m"
NC := "\e[0m"

# Shell functions
INFO := @bash -c '\
    printf $(YELLOW); \
    echo "=> $$1"; \
    printf $(NC)' VALUE

# Get container id of application service container
APP_CONTAINER_ID := $$(docker-compose -p $(REL_PROJECT) -f $(REL_COMPOSE_FILE) ps -q $(APP_SERVICE_NAME))

# Get image id of application service
IMAGE_ID := $$(docker inspect -f '{{ .Image }}' $(APP_CONTAINER_ID))

# Extract tag arguments
ifeq (tag, $(firstword $(MAKECMDGOALS)))
    TAG_ARGS := $(wordlist 2, $(words $(MAKECMDGOALS)), $(MAKECMDGOALS))
    ifeq ($(TAG_ARGS),)
        $(error You must specify a tag)
    endif
    $(eval $(TAG_ARGS):;@:) # line 108 Do not interpret "0.1 latest whatever" as make target files 
endif

Below is the error on running make command:

$ make tag 0.1 latest $(git rev-parse --short HEAD)
Makefile:108: *** recipe commences before first target.  Stop.

Line 108, purpose of $(eval $(TAG_ARGS):;@:) to convey that 0.1 latest $(git rev-parse --short HEAD) are not make targets.


Why $(eval $(TAG_ARGS):;@:) gives error?

Upvotes: 0

Views: 5977

Answers (2)

user10678532
user10678532

Reputation:

That particular error happens because your $(eval ...) line is indented by a TAB (something that it's hidden by this horribly broken web interface).

Example:

$ make -f <(printf '\t$(eval foo:;echo yup)')
/dev/fd/63:1: *** recipe commences before first target.  Stop.

# now with spaces instead of TAB
$ make -f <(printf '    $(eval foo:;echo yup)')
echo yup
yup

The error is documented in the make manual:

recipe commences before first target. Stop.

This means the first thing in the makefile seems to be part of a recipe: it begins with a recipe prefix character and doesn't appear to be a legal make directive (such as a variable assignment). Recipes must always be associated with a target.

The "recipe prefix character" is TAB by default.

$ make -f <(printf '\tfoo')
/dev/fd/63:1: *** recipe commences before first target.  Stop.

It doesn't have to be the "first thing in the makefile", though: the same error will trigger after a number of rules, if preceded by a directive like a macro assignment or such:

$ make -f <(printf 'all:;\nkey=val\n\tfoo')
/dev/fd/63:3: *** recipe commences before first target.  Stop.

And even if a macro expands to an empty string, GNU make will not consider empty a line containing just macros expanding to empty strings:

$ make -f <(printf '\t\nfoo:;@:')
$ make -f <(printf '\t$(info foo)\nfoo:;@:')
/dev/fd/63:1: *** recipe commences before first target.  Stop.
$ make -f <(printf '   $(info foo)\nfoo:;@:')
foo

Upvotes: 4

MadScientist
MadScientist

Reputation: 101131

I can't reproduce this problem. I put your last ifeq statement into a makefile and it works fine for me with GNU make 4.1 and 4.2.1. There must be something more unusual about your situation.

The classic way to debug issues with eval is to duplicate the line and replace the eval with info; this way make will print out exactly what it sees. Often this will show you what is wrong.

There are other confusing things about this makefile.

First, why are you using eval here in the first place? Why not just write the rule directly? There's nothing wrong with:

$(TAG_ARGS):;@:

no need to wrap it in an eval.

Second, why are you using := then escaping the variables? Why not just use = instead and not bother with the escapes?

INSPECT = $(docker-compose -p $1 -f $2 ps -q $3 | xargs -I ARGS docker inspect -f "{{ .State.ExitCode }}" ARGS)

works just fine.

Finally, I strongly urge you to not add @ to your recipes. It makes debugging makefiles very difficult and frustrating. Instead consider using a method such as Managing Recipe Echoing to handle this.

Upvotes: 1

Related Questions