[为x86_64-pc-linux-gnu构建的GNU Make 4.1
下面是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
以下是运行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):;@:)
出错?
$(eval $(TAG_ARGS):;@:)
不能达到目的,因为它已被注释掉:# $(eval $(TAG_ARGS):;@:) # Do not interpret "0.1 latest whatever" as make target files
语法着色可以帮助发现这种情况。
ifeq
语句放入了一个makefile中,对于GNU make 4.1和4.2.1,它对我来说很好用。您的情况肯定有一些更不寻常的情况。调试eval
问题的经典方法是复制该行,并将eval
替换为info
;这样一来,make就会完全打印出所看到的内容。通常,这会向您显示出什么问题。
关于此makefile还有其他令人困惑的事情。
首先,为什么首先在这里使用eval
?为什么不直接写规则呢?没错:
$(TAG_ARGS):;@:
无需将其包装在eval
中。[其次,为什么要使用
:=
然后转义变量?为什么不只使用=
而又不去使用转义符?
INSPECT = $(docker-compose -p $1 -f $2 ps -q $3 | xargs -I ARGS docker inspect -f "{{ .State.ExitCode }}" ARGS)
效果很好。最后,我强烈建议您不要在食谱中添加
@
。这使得调试makefile非常困难且令人沮丧。而是考虑使用诸如Managing Recipe Echoing之类的方法来处理此问题。
$(eval ...)
行被TAB缩进了(某些东西被这个可怕的Web界面隐藏了。)>示例:
$ 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.
这意味着makefile中的第一件事似乎是食谱:它以
食谱前缀字符
开头,没有出现成为合法的make
指令(例如变量分配)。配方必须始终与目标关联。默认情况下,“食谱前缀字符”为TAB。
$ make -f <(printf '\tfoo')
/dev/fd/63:1: *** 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