O make é uma “ferramenta de automação de compilação” segundo a wikipedia. Mas ela pode ser útil mesmo quando não é preciso compilar nada.

É possível usar o make como um executor de comandos. A vantagem de usar o make para fazer isso é por ser comumente encontrada em distribuições Linux. Além de fácil de ler, escrever e manter um makefile para esse objetivo.

Um exemplo de utilização pode ser encontrado no projeto pydantic. Vou usar como exemplo o makefile na versão 1.9.0 da lib:

.DEFAULT_GOAL := all
isort = isort pydantic tests
black = black -S -l 120 --target-version py38 pydantic tests

[...]

.PHONY: lint
lint:
	flake8 pydantic/ tests/
	$(isort) --check-only --df
	$(black) --check --diff

[...]

.PHONY: mypy
mypy:
	mypy pydantic

[...]

.PHONY: test
test:
	pytest --cov=pydantic

.PHONY: testcov
testcov: test
	@echo "building coverage html"
	@coverage html

[...]

.PHONY: all
all: lint mypy testcov

[...]

Como o make executa uma regra

Considerando que estou na pasta raiz do projeto do pydantic e executo o comando:

$ make

O make procura a primeira regra no arquivo para começar a executar. Isso é chamado default goal.

No makefile do pydantic, ele usa a variável especial chamada .DEFAULT_GOAL para apontar para qual regra o make deve executar primeiro. Apesar das regras de lint, mypy e testcov estarem escritas antes da regra all elas serão executadas depois.

Como executar uma regra específica

Caso eu queira executar só a regra de testcov:

$ make testcov

Nesse caso o make vai executar apenas a regra testcov e seu target com dependência test.

Targets .PHONY

A sintaxe .PHONY é utilizada para sinalizar que o target não é um nome de arquivo. Isso evita um possível conflito com o nome de um arquivo real e otimiza desempenho. A sintaxe:

.PHONY nome_da_acao
nome_da_acao: dependecias
  comandos
  ...

Imprimir comando da saída

O comportamento comum do make é que todo comando executado é enviado para saída padrão. Uma maneira de evitar isso é usar a flag -s ou --silent:

$ make --silent

Caso queira que apenas exiba e não execute os comandos, existem a flag -n ou --just-print:

$ make --just-print

Comando help

Já saindo do exemplo do makefile do pydantic e entrando em outras dicas.

Fuçando na web existem vários exemplos de maneiras de adicionar um comando de help automaticamente num makefile. Um dos mais interessantes que encontrei foi esse:

.PHONY: help
help:
	@echo "$$(tput bold)Comandos:$$(tput sgr0)"
	@echo
	@sed -n -e "/^## / { \
		h; \
		s/.*//; \
		:doc" \
		-e "H; \
		n; \
		s/^## //; \
		t doc" \
		-e "s/:.*//; \
		G; \
		s/\\n## /---/; \
		s/\\n/ /g; \
		p; \
	}" ${MAKEFILE_LIST} \
	| LC_ALL='C' sort --ignore-case \
	| awk -F '---' \
		-v ncol=$$(tput cols) \
		-v indent=19 \
		-v col_on="$$(tput setaf 6)" \
		-v col_off="$$(tput sgr0)" \
	'{ \
		printf "%s%*s%s ", col_on, -indent, $$1, col_off; \
		n = split($$2, words, " "); \
		line_length = ncol - indent; \
		for (i = 1; i <= n; i++) { \
			line_length -= length(words[i]) + 1; \
			if (line_length <= 0) { \
				line_length = ncol - indent - length(words[i]) - 1; \
				printf "\n%*s ", -indent, " "; \
			} \
			printf "%s ", words[i]; \
		} \
		printf "\n"; \
	}' \
	| more $(shell test $(shell uname) == Darwin && echo '--no-init --raw-control-chars')

Assim podemos popular o comando de help da seguinte maneira:

.PHONY: oi-mundo
## Mostra mensagem de oi mundo no terminal
oi-mundo:
	@echo "oi mundo"


.PHONY: nao-aparecer-no-help
# Essa não vai aparecer no help
nao-aparecer-no-help:
	@echo "Não mostra no help"

Temos então o resultado:

$ make help
Comandos:

oi-mundo            Mostra mensagem de oi mundo no terminal

Dividindo comandos em arquivos diferentes

O make tem uma opção onde é possível passar qual arquivo vai ser lido pelo comando:

$ make -f NomeDoArquivo.mk

Por convenção usei a extensão .mk para outros arquivos de makefile no mesmo projeto.

Nesse projeto de exemplo resolvi separar o executor de comandos do makefile que compila os arquivos de código-fonte. Considerando a estrutura de pastas:

├── src
│   └── main.c
├── Makefile
└── MakeGb.mk

No makefile principal, criei o comando:

gb:
	@ make -f MakeGb.mk all

Então ao dar um make gb, o make apenas compila os arquivos código-fonte.

Referências