What does a phony docker.* target mean in a Makefile? - makefile

What is the meaning of this make target:
.PHONY: docker.%
docker.%:
go mod tidy
docker build -t $* .
docker run -p 5751:5751 -v $$(pwd)/:/work $* /work/config.yaml
In particular, what is the meaning of % in docker.% and how is it being utilized here?

Related

If statement inside of Makefile to detect docker image exist and if so delete it

I have a simple if statement inside of Makefile to say that delete docker image if it exists but it doesn't work. Notice in the log file image exists but when I do make clean if statement fails.
NAME=program1
all: .docker-build
docker run ${NAME} make all
.docker-build:
docker build . -t ${NAME}
#echo "" > .docker-build
run-%: .docker-build
docker run ${NAME} make $*
clean:
ifeq ($(docker images -q ${NAME} 2> /dev/null), "")
docker image rm -f ${NAME}
endif
#rm -f .docker-build
Log (notice image exist but if statement inside of make clean fails):
➜ dockerfile-test docker images -q program1 2> /dev/null
5ee4797b91ad
➜ dockerfile-test make clean
rm -f .docker-build
This:
ifeq ($(docker images -q ${NAME} 2> /dev/null), "")
expands the make variable named docker images -q program1 2> /dev/null, which doesn't exist and so expands to the empty string, then it compares it to the two-character string "" and it never matches.
You probably meant:
ifeq ($(shell docker images -q ${NAME} 2> /dev/null),)

Makefile target does not recognize dependency action

I wrote this simple Makefile to illustrate my problem.
$make target
calls the dep as a dependency and pulls the image
But the subsequent check for docker image list -q $(IMG) does not find my image.
What is happening here and how should I fix this?
IMG := hello-world
.PHONY: target
target: dep
ifeq ($(shell docker image list -q $(IMG)),)
echo "docker image list did not recognize the pull"
endif
.PHONY: dep
dep:
#docker pull $(IMG)
That test isn't subsequent. It's substituted into the Makefile when it's read, before any rules are executed.
You probably want to perform that test in the commands of the target rule:
target: dep
if test -z "$$(docker image list -q $(IMG))"; then \
echo "docker image list did not recognize the pull" >&2; \
false; \
fi
We could change the command to just run docker image inspect - that will return a true status if the image exists, and false otherwise:
target: dep
if ! docker image inspect "$(IMG))" >/dev/null 2>&1; then \
echo "docker image list did not recognize the pull" >&2; \
false; \
fi

Docker build fails when executed within Makefile

So, I have a Docker build command that I have tested which works great
docker build \
-t app \
--no-cache --network host \
--build argssh_private_key="$(cat ~/.ssh/id_rsa)"\
--build-arg python_version="3.6.8" -f Dockerfile .
To ease the pain of the team learning Docker I encapsulated a few of the commands - build, start, stop - within a Makefile. However, within the Makefile I need to change the command slightly by modifying
$(cat ~/.ssh/id_rsa)
to
$(shell cat ~/.ssh/id_rsa)
When I execute the following:
make build
I receive the following message:
Step 13/20 : RUN git clone --depth 1 "${git_user}#${git_host}:${git_repo}" app
---> Running in d2eb41a71315
Cloning into 'app'...
Warning: Permanently added the ECDSA host key for IP address [ip_address] to the list of known hosts.
Permission denied (publickey).
fatal: Could not read from remote repository.
Please make sure you have the correct access rights and the repository exists.
However, I do not have the same issue when executing from the command-line. I I think it has something to do with the way the call the "cat" command but, I do not know a way to resolve.
Any ideas ?
Makefile:
APP_NAME=ccs_data_pipeline
DATA?="${HOME}/data"
DOCKER_FILE=Dockerfile
PYTHON_VERSION?=3.6.8
SRC?=$(shell dirname `pwd`)
PRIVATE_KEY?=$(shell echo $(shell cat ~/.ssh/id_rsa))
build: ## Build container for ccs data pipeline
docker build \
-t $(APP_NAME) \
--no-cache --network host \
--build-arg ssh_private_key="$(PRIVATE_KEY)" \
--build-arg python_version="$(PYTHON_VERSION)" \
-f $(DOCKER_FILE) .
start: ## Start the docker container
docker run \
-it -v $(DATA):/data \
--network host \
--rm \
--name="$(APP_NAME)" $(APP_NAME)
stop: ## Stop the docker container
docker stop $(APP_NAME); \
docker rm $(APP_NAME)
Please show your actual makefile, or at least the entire rule that is having the error. The single command you provided, with no context, is not enough to understand what you're doing or what might be wrong.
Note that it is often not correct to replace a shell operation like $(...) with a make shell command $(shell ...). However, sometimes it will work "by accident", where the real differences between those commands don't happen to matter.
In general you should never use $(shell ...) inside a recipe (I have no idea if this command appears in a recipe). Instead, you should escape all the dollar signs that you want to be passed verbatim to the shell when it runs your recipe:
$$(cat ~/.ssh/id_rsa)

Makefile: run the same command with different arguments on different targets

Code
Consider the following makefile snippet:
COMMIT := $(shell git rev-parse HEAD)
build:
docker build -f Dockerfile --no-cache=false -t $(COMMIT) .
rebuild:
docker build -f Dockerfile --no-cache=true -t $(COMMIT) .
The problem
The only difference between build and rebuild is the value of the --no-cache parameter. Obviously, rewriting the same command with a slight change is a bad practice; it breaks the DRY principle, and if I ever need to change something else in the command - for example, the value of -t - I would have to change it across all relevant targets.
I had something like this in mind:
COMMIT := $(shell git rev-parse HEAD)
NO_CACHE := false
build:
docker build -f Dockerfile --no-cache=$(NO_CACHE) -t $(COMMIT) .
rebuild:
NO-CACHE = true
make build
I tried playing with the variables, with no luck.
My question
What would be an elegant way to write the docker build command once, and have each target alter its parameter?
You can use constructed variable names:
COMMIT := $(shell git rev-parse HEAD)
build_NOCACHE = false
rebuild_NOCACHE = true
build rebuild:
docker build -f Dockerfile --no-cache=$($#_NOCACHE) -t $(COMMIT) .
Or you can use target-specific variables:
COMMIT := $(shell git rev-parse HEAD)
build: NOCACHE = false
rebuild: NOCACHE = true
build rebuild:
docker build -f Dockerfile --no-cache=$(NOCACHE) -t $(COMMIT) .
Use the call function
Positional arguments are specified from 1 to n and used in the command definition as $(1), $(2), $(n).
COMMIT := $(shell git rev-parse HEAD)
DOCKER_BUILD_CMD = docker build -f Dockerfile --no-cache=$(1) -t $(COMMIT) .
build:
$(call DOCKER_BUILD_CMD, false)
rebuild:
$(call DOCKER_BUILD_CMD, true)

How to execute a make target on file change automatically?

How do I write a make target that will watch for any file changes in specific folders and execute some other make target to compile files? I am looking for a way that can do this with minimal dependency on tools in addition to make itself to keep things simple.
For the watching you can use fswatch. (There's also a go version of this program which may be easier to install: fswatch) For example:
fswatch -ext cpp,c,h make -f Makefile
Anytime you change a cpp, c or h file it will run make again.
Make can be a bit slow for this, so I tend to use ninja instead, but that really depends on the size of your project.
Another option is tup, which has watching built-in:
tup monitor
But, sadly, only for linux.
You can use entr and adjust your Makefile similar to this one
.DEFAULT_GOAL := run
SHELL := /bin/bash
run:
clear && \
cp one.txt two.txt && \
rm -f _* *.l2m *.o2m && \
Ganlib < testgan2.x2m
watch:
while sleep 1 ; do find . -name '*.x2m' -o -name '*.c2m' \
| entr -d make -f ./Makefile ; done
.PHONY: run watch
followed by
$ make watch

Resources