Notes to Self

Alex Sokolsky's Notes on Computers and Programming

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.

Tutorial

Summary of automatic variables

Manual on 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

Makefile Support in VS Code

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

c-simple.mak:

#
# 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

c.mak:

# 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)))

cpp.mak:

#
# 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

jekyll.mak

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

packer.mak

.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 ''

python.mak

.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

terraform.mak

.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: