make and Makefile
My definition: make
is a data-driven (data is in a Makefile
) filter for
building ‘targets’.
GNU man page, manual, quick reference.
Summary of automatic variables
Variable | Description |
---|---|
$@ |
the target name |
$% |
the target member name, when the target is an archive member |
$< |
just the first prerequisite |
$? |
all the prerequisites newer than the target |
$^ |
all the prerequisites |
.PHONY |
Adding .PHONY to a target will prevent make from confusing the phony target with a file name. manual, one of many special built-in targets |
Support in VSCode
Verify the file exists
Add this to your Makefile:
#
# check the prerequisites
#
EXECUTABLES := exec1 exec2
K:=$(foreach exec,$(EXECUTABLES),\
$(if $(shell which $(exec)),'',$(error "No $(exec) in PATH")))
Verify the git hook installed
Add this to your Makefile:
SHELL:=/bin/bash
export PROJECT_ROOT=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
PATH_TO_HOOK:=$(PROJECT_ROOT)/.git/hooks/pre-commit
ifeq ("$(wildcard $(PATH_TO_HOOK))","")
$(error git hook not installed)
endif
Examples
#
# simple C program build
#
CC=gcc
CFLAGS=-g
RM=rm -f
default: all
all: build
build: hello.c
$(CC) $(CFLAGS) -o hello hello.c
clean:
$(RM) hello
# The name of the source files
SOURCES = main.c foo.c bar.c
# The name of the executable
EXE = main
# Flags for compilation (adding warnings are always good)
CFLAGS = -g -Wall
# Flags for linking, e.g. -L./lib
LDFLAGS =
# Libraries to link with, e.g. -lcurl
LIBS = -lcurl
# Use the GCC frontend program when compiling
CC = gcc
# Use the GCC frontend program when linking
LD = gcc
# place to store auto-generated dependencies
DEPDIR = .d
#
# Nothing to change below
#
# This creates a list of object files from the source files
OBJECTS = $(SOURCES:%.c=%.o)
.PHONY: all
# Having an "all" target is customary, so one could write "make all"
# It depends on the executable program
all: $(EXE)
# The first target, this will be the default target if none is specified
# This target tells "make" to make the "all" target
default: all
# This will link the executable from the object files
$(EXE): $(OBJECTS)
$(LD) $(LDFLAGS) $(OBJECTS) $(LIBS) -o $(EXE)
# Target to clean up after us
.PHONY: clean
clean:
# Remove the executable file
rm -f $(EXE) $(OBJECTS)
# Remove the dependencies and symbols
rm -Rf $(DEPDIR) $(EXE).dSYM
# Advanced auto-dependency, from:
# http://make.mad-scientist.net/papers/advanced-auto-dependency-generation/
$(shell mkdir -p $(DEPDIR) >/dev/null)
DEPFLAGS = -MT $@ -MMD -MP -MF $(DEPDIR)/$*.Td
COMPILE.c = $(CC) $(DEPFLAGS) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
POSTCOMPILE = mv -f $(DEPDIR)/$*.Td $(DEPDIR)/$*.d
%.o : %.c
%.o : %.c $(DEPDIR)/%.d
$(COMPILE.c) $(OUTPUT_OPTION) $<
$(POSTCOMPILE)
$(DEPDIR)/%.d: ;
.PRECIOUS: $(DEPDIR)/%.d
-include $(patsubst %,$(DEPDIR)/%.d,$(basename $(SOURCES)))
#
# Sample Makefile for cpp project
#
CC=gcc
CXX=g++
RM=rm -f
CPPFLAGS=-g -Wall
#LDFLAGS=-g -L../lib
LDFLAGS=-g
#LDLIBS=-lmylib -lm
LDLIBS=
SRCS=$(shell find . -name "*.cpp")
OBJS=$(subst .cpp,.o,$(SRCS))
all: main
main: $(OBJS)
$(CXX) $(LDFLAGS) -o main $(OBJS) $(LDLIBS)
depend: .depend
.depend: $(SRCS)
$(RM) ./.depend
$(CXX) $(CPPFLAGS) -MM $^>>./.depend;
clean:
$(RM) $(OBJS) main
distclean: clean
$(RM) tool /.depend
include .depend
default: help
all: install build
h help:
@grep '^[a-z]' Makefile
install:
bundle config set --local path vendor/bundle
bundle install
upgrade: # update `Gemfile.lock`
bundle update
s serve: # launch locally
bundle exec jekyll serve --trace --livereload
build:
JEKYLL_ENV=production bundle exec jekyll build --trace
clean:
rm -rf _site .bundle .jekyll-cache vendor
.DEFAULT_GOAL := help
export TEMPLATE ?=
export PACKAGE = $(TEMPLATE)
export AWS_TARGET_AMI = $(PACKAGE)-all-$(shell date '+%Y%m%d%H%M%S')-focal
export PACKER_DRY_RUN ?= true
export PACKER_DEBUG ?= 0
export PACKER_OPTS ?= -on-error=ask -timestamp-ui -var packages=$(PACKAGE) -var aws_target_ami=$(AWS_TARGET_AMI)
ifeq ($(PACKER_DEBUG), 1)
PACKER_OPTS += -debug
endif
export GOSS_PROVISIONER_VERSION=v3.1.3
export AWS_DEFAULT_REGION ?= us-east-1
PACKER_SCRATCH_DIR=/tmp/packer
#
# Build
#
bake: .guard-TEMPLATE .install validate-shell validate-packer ## Generate an AMI
@rm -f build-$(TEMPLATE).log
@echo "Building AMI, logging into build-$(TEMPLATE).log..."
> build-$(TEMPLATE).log
@ if [ "${PACKER_DRY_RUN}" = "true" ]; then \
echo "> Skipping creation and publishing of final AMI"; \
PACKER_LOG=1 PACKER_LOG_PATH=build-$(TEMPLATE).log packer build -var 'aws_skip_create_ami=true' $(PACKER_OPTS) "$(TEMPLATE).pkr.hcl"; \
else \
echo "> Ensure creation and publishing of final AMI"; \
PACKER_LOG=1 PACKER_LOG_PATH=build-$(TEMPLATE).log packer build -var 'aws_skip_create_ami=false' $(PACKER_OPTS) "$(TEMPLATE).pkr.hcl"; \
fi
# @rm -f build-$(TEMPLATE).log
#
# Validation
#
validate: .install validate-shell validate-packer ## Run all validation routines
validate-shell: ## Run shellcheck for all scripts in scripts dir
find scripts/ -type f | xargs shellcheck
validate-packer: ## Validate packer templates
ls ami-* | xargs -I{} packer validate -var packages=$(PACKAGE) -var aws_target_ami=$(AWS_TARGET_AMI) {}
#
# Utils
#
.install:
@mkdir -p "${PACKER_SCRATCH_DIR}" || true
@test -f "${PACKER_SCRATCH_DIR}/packer-provisioner-goss.tgz" || curl -L -o "${PACKER_SCRATCH_DIR}/packer-provisioner-goss.tgz" https://github.com/YaleUniversity/packer-provisioner-goss/releases/download/${GOSS_PROVISIONER_VERSION}/packer-provisioner-goss-${GOSS_PROVISIONER_VERSION}-$(or $(shell uname -s | tr A-Z a-z), linux)-amd64.tar.gz
@tar xfz "${PACKER_SCRATCH_DIR}/packer-provisioner-goss.tgz" -C /tmp/ && mv /tmp/packer-provisioner-goss .
@chmod +x "packer-provisioner-goss"
.guard-%:
@ if [ "${${*}}" = "" ]; then \
printf "\033[0;31m[!] Variable '$*' not set\nSet it with:\n make $*=<value> target\033[0m\n\n"; \
make help; \
exit 1; \
fi
.PHONY: bake validate validate-packer validate-shell
help: ## Shows the help
@echo 'Usage: make <OPTIONS> ... <TARGETS>'
@echo ''
@echo 'Available targets are:'
@echo ''
@grep -E '^[ a-zA-Z_-]+:.*?## .*$$' $(shell echo "$(MAKEFILE_LIST)" | tr " " "\n" | sort -r | tr "\n" " ") \
| sed 's/Makefile[a-zA-Z\.]*://' | sed 's/\.\.\///' | sed 's/.*\///' | \
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-25s\033[0m %s\n", $$1, $$2}'
@echo ''
.DEFAULT_GOAL := help
SHELL:=/bin/bash
export PROJECT_ROOT = $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
# define the name of the virtual environment directory
VENV:=.venv
PYTHON=$(VENV)/bin/python3
PIP=$(VENV)/bin/pip
# targets which are NOT files
.PHONY: help venv run test clean build
help: ## Shows the help
@echo 'Usage: make <TARGETS>'
@echo ''
@echo 'Available targets are:'
@echo ''
@grep -E '^[ a-zA-Z_-]+:.*?## .*$$' $(shell echo "$(MAKEFILE_LIST)" | tr " " "\n" | sort -r | tr "\n" " ") \
| sed 's/Makefile[a-zA-Z\.]*://' | sed 's/\.\.\///' | sed 's/.*\///' | \
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-25s\033[0m %s\n", $$1, $$2}'
@echo ''
# venv is a shortcut target
venv: $(VENV)/bin/activate ## Activate the venv
$(VENV)/bin/activate: requirements.txt
python3 -m venv $(VENV)
$(PIP) install -r requirements.txt
run: venv ## Execute python program
$(PYTHON) main.py $(SITE)
test: venv ## Execute python tests
$(PYTHON) -m unittest -v *_test.py
clean: ## Cleanup the artifacts
rm -rf $(VENV) .mypy_cache
find . -name __pycache__ | xargs rm -rf
DOCKER_USERNAME ?= john.doe
APPLICATION_NAME ?= da-app
GIT_HASH ?= $(shell git log --format="%h" -n 1)
build: ## Build docker image
docker build --tag ${DOCKER_USERNAME}/${APPLICATION_NAME}:${GIT_HASH} .
release: ## Release docker image
cat ./.docker-password | docker login --username ${DOCKER_USERNAME} --password-stdin
docker push ${DOCKER_USERNAME}/${APPLICATION_NAME}:${GIT_HASH}
docker pull ${DOCKER_USERNAME}/${APPLICATION_NAME}:${GIT_HASH}
docker tag ${DOCKER_USERNAME}/${APPLICATION_NAME}:${GIT_HASH} ${DOCKER_USERNAME}/${APPLICATION_NAME}:latest
docker push ${DOCKER_USERNAME}/${APPLICATION_NAME}:latest
.DEFAULT_GOAL := help
SHELL:=/bin/bash
export PROJECT_ROOT = $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
.PHONY: help format docs clean
help: ## Shows the help
@echo 'Usage: make <TARGETS>'
@echo ''
@echo 'Available targets are:'
@echo ''
@grep -E '^[ a-zA-Z_-]+:.*?## .*$$' $(shell echo "$(MAKEFILE_LIST)" | tr " " "\n" | sort -r | tr "\n" " ") \
| sed 's/Makefile[a-zA-Z\.]*://' | sed 's/\.\.\///' | sed 's/.*\///' | \
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-25s\033[0m %s\n", $$1, $$2}'
@echo ''
format: ## Run Terraform format
terraform fmt -recursive $(PROJECT_ROOT)
.guard-%:
@ if [ "${${*}}" = "" ]; then \
printf "\033[0;31m[!] Variable '$*' not set\nSet it with:\n make $*=<value> target\033[0m\n\n"; \
make help; \
exit 1; \
fi
docs: .guard-MODULE_NAME ## Build the module documentation
@echo "> Generating module documentation"
@sed -i'.bak' '/### Module Documentation/,$$d' $(PROJECT_ROOT)/modules/$(MODULE_NAME)/README.md && rm -f $(PROJECT_ROOT)/modules/$(MODULE_NAME)/README.md.bak
@printf "### Module Documentation\n\n_Note: This is autogenerated_\n\n" >> $(PROJECT_ROOT)/modules/$(MODULE_NAME)/README.md
@docker run --rm -v $(PROJECT_ROOT):/data quay.io/terraform-docs/terraform-docs:0.16.0 \
markdown table /data/$(MODULE_NAME)/ >> $(PROJECT_ROOT)/modules/$(MODULE_NAME)/README.md
clean: ## Do local clean of terra(form|grunt) artifacts
find $(PROJECT_ROOT) -type f -name .terraform.lock.hcl -delete
find $(PROJECT_ROOT) -type d -name .terragrunt-cache -prune -exec rm -fr {} \;
World class: