Makefile:108: *** 配方在第一个目标之前开始

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

GNU Make 4.1 为 x86_64-pc-linux-gnu

构建

下面是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 $ -f $ ps -q $ | 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 "=> $"; \
    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

下面是 运行 make 命令的错误:

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

第 108 行,$(eval $(TAG_ARGS):;@:) 的目的是传达 0.1 latest $(git rev-parse --short HEAD) 不是 make 目标。


为什么$(eval $(TAG_ARGS):;@:)报错?

我无法重现这个问题。我将您的最后一个 ifeq 语句放入一个 makefile 中,它对我来说在 GNU make 4.1 和 4.2.1 中运行良好。你的情况一定有更不寻常的地方。

调试 eval 问题的经典方法是复制该行并将 eval 替换为 info;这样 make 将准确打印出它所看到的内容。通常这会告诉你哪里出了问题。

这个 makefile 还有其他令人困惑的地方。

首先,您为什么首先在这里使用 eval?为什么不直接写规则呢?没有错:

$(TAG_ARGS):;@:

无需将其包装在 eval.

其次,为什么要使用 := 然后转义变量?为什么不直接使用 = 而不用担心转义?

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

工作正常。

最后,我强烈建议您不要将 @ 添加到您的食谱中。它使调试 makefile 变得非常困难和令人沮丧。相反,请考虑使用 Managing Recipe Echoing 等方法来处理此问题。

发生该特定错误是因为您的 $(eval ...) 行被 TAB 缩进(它被这个可怕的损坏的网络界面隐藏了)。

示例:

$ 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

错误记录在 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.

"recipe prefix character"默认为TAB

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

它不一定是 "first thing in the makefile",但是:如果前面有像宏赋值这样的指令,同样的错误将在许多规则之后触发:

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

即使宏展开为空字符串,GNU make 也不会考虑空行,其中仅包含展开为空字符串的宏:

$ 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