Skip to content

Make

A Makefile is used to determine which pieces of the program need to be recompiled. The Makefile is run using the make command.

Tip

If the make command is not available, the tool can be installed using the system's package manager, e.g. sudo apt install make -y.

Why use a Makefile?

Honestly, I primarily use these in conjunction with compiled languages, e.g. within my Rust or Mojo projects. They make more sense in those contexts (no need to use .PHONY everywhere), although for Rust, cargo-make arguably does the job better. The main reason I have chosen to include one in this project is to demonstrate their application.

The make tool incorporates logic to determine what to recompile and what not to recompile (or re-transpile). For example, if a source file has not been changed, it will not be recompiled.

This seems trivial in a small project, but it makes a tremendous difference in build times for large projects. Furthermore, you can use Makefile to make specific command chains (recipes) dependant on another.

Recipe: Default

The help recipe has been marked as the default recipe. Hence, it will show when calling make without a recipe specified.

default: help

.PHONY: help
help: # Show help for each recipe.
    @grep -E '^[a-zA-Z0-9 -]+:.*#'  Makefile | sort | while read -r l; do printf "\033[1;32m$$(echo $$l | cut -f 1 -d':')\033[00m:$$(echo $$l | cut -f 2- -d'#')\n"; done
Recipe: Default
make
Usage:
    make <recipe>

Available recipes:
    help                            Show help for each recipe.
    pkg-install                     Installs the project.
    pkg-update                      Updates the project's dependencies to their latest versions.
    pkg-types                       Build the typing stubs for the project.
    pkg-compile                     Compiles the project into sdist and wheel.
    pc-install                      Installs the pre-commit hooks.
    pc-update                       Updates the pre-commit hooks to their latest version.
    pc-run                          Run pre-commit hooks on all files immediately.
    md-serve                        Run MkDocs' builtin development server.
    md-build                        Build the MkDocs documentation.

Recipe: Package setup

The all recipe installs the project, installs the pre-commit hooks and then updates the pre-commit hooks to their latest version.

.PHONY: all
all: pkg-install pc-install pc-update

.PHONY: pkg-install
pkg-install: # Installs the project.
    poetry install

.PHONY: pc-install
pc-install: pkg-install # Installs the pre-commit hooks.
    poetry run pre-commit install

.PHONY: pc-update
pc-update: pc-install # Updates the pre-commit hooks to their latest version.
    poetry run pre-commit autoupdate
Recipe: Set up package
make all
poetry install
Installing dependencies from lock file

Package operations: 3 installs, 0 updates, 0 removals

  • Installing iniconfig (2.0.0)
  • Installing pluggy (1.4.0)
  • Installing pytest (7.4.4)

Installing the current project: mkdocs-demo (1.1.0)
poetry run pre-commit install
pre-commit installed at .git/hooks/pre-commit
poetry run pre-commit autoupdate
[https://github.com/pre-commit/pre-commit-hooks] already up to date!
[https://github.com/astral-sh/ruff-pre-commit] already up to date!
[https://github.com/pre-commit/mirrors-mypy] already up to date!

Recipe: Run pre-commit hooks

The pc-run recipe executes the pre-commits hooks on all files on demand.

Info

This recipe depends on the pc-install recipe, which in turn depends on the pkg-install recipe. In other words, make ensures the package is installed, the pre-commit hooks are installed and then executes the pc-run recipe.

.PHONY: pc-run
pc-run: pc-install # Run pre-commit hooks on all files immediately.
    poetry run pre-commit run --all-files
Recipe: Run pre-commit hooks
poetry install
Installing dependencies from lock file

No dependencies to install or update

Installing the current project: mkdocs-demo (1.1.0)
poetry run pre-commit install
pre-commit installed at .git/hooks/pre-commit
poetry run pre-commit run --all-files
[INFO] Initializing environment for https://github.com/pre-commit/pre-commit-hooks.
[INFO] Initializing environment for https://github.com/astral-sh/ruff-pre-commit.
[INFO] Initializing environment for https://github.com/pre-commit/mirrors-mypy.
[INFO] Installing environment for https://github.com/pre-commit/pre-commit-hooks.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Installing environment for https://github.com/astral-sh/ruff-pre-commit.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
[INFO] Installing environment for https://github.com/pre-commit/mirrors-mypy.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
fix end of files.........................................................Passed
trim trailing whitespace.................................................Failed
- hook id: trailing-whitespace
- exit code: 1
- files were modified by this hook

Fixing .github/ISSUE_TEMPLATE/bug_report.yml
Fixing TODO.md
Fixing .github/ISSUE_TEMPLATE/feature_request.yml

check yaml...............................................................Passed
check python ast.........................................................Passed
check builtin type constructor use.......................................Passed
check docstring is first.................................................Passed
check json...............................................................Passed
check for merge conflicts................................................Passed
check toml...............................................................Passed
debug statements (python)................................................Passed
detect private key.......................................................Passed
fix end of files.........................................................Passed
fix utf-8 byte order marker..............................................Passed
fix python encoding pragma...............................................Passed
mixed line ending........................................................Passed
Ruff Linter..............................................................Passed
Ruff Formatter...........................................................Passed
Mypy.....................................................................Passed
make: *** [Makefile:35: pc-run] Error 1

Complete Makefile

Check out the full Makefile here.

default: help

.PHONY: all pkg-install pkg-update pkg-compile pkg-types pc-install pc-update pc-run md-serve md-build

help: ## Show help for each recipe.
    @echo "Usage:\n\tmake <recipe>"
    @echo "\nAvailable recipes:"
    @awk 'BEGIN {FS = ":.*##"; } /^[$$()% a-zA-Z_-]+:.*?##/ \
    { printf "\t\033[36m%-30s\033[0m %s\n", $$1, $$2 } /^##@/ \
    { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)

all: pkg-install pc-install pc-update

# Package recipes
pkg-install: ## Installs the project.
    poetry install

pkg-update: pkg-install ## Updates the project's dependencies to their latest versions.
    poetry update

pkg-types: pkg-install ## Build the typing stubs for the project.
    stubgen -p mkdocs_demo -o typings

pkg-compile: pkg-install pkg-types ## Compiles the project into sdist and wheel.
    poetry build

# Pre-commit recipes
pc-install: pkg-install ## Installs the pre-commit hooks.
    poetry run pre-commit install

pc-update: pc-install ## Updates the pre-commit hooks to their latest version.
    poetry run pre-commit autoupdate

pc-run: pc-install ## Run pre-commit hooks on all files immediately.
    poetry run pre-commit run --all-files

# MkDocs recipes
md-serve: all ## Run MkDocs' builtin development server.
    poetry run mkdocs serve --config-file src/mkdocs_demo/mkdocs.yml

.DELETE_ON_ERROR:
md-build: all pkg-update ## Build the MkDocs documentation.
    poetry run mkdocs build --config-file src/mkdocs_demo/mkdocs.yml --clean --use-directory-urls

Alternative: poetry scripts

Within poetry's pyproject.toml, you can define scripts. You can define these scripts under the [tool.poetry.scripts] header.

Subsequently, after running poetry install to register the script, you can run them using poetry run <script_name>.

Info

The scripts in poetry are quite similar to those in a package.json file, for those familiar with NodeJS projects.

However, the main drawback of these scripts is that they require a specific entrypoint within a source file (e.g., gen-typings = "src.mkdocs_demo.scripts.mypy:gen_types").

Moreover, they will run "naively". Specifically, they will transpile/compile every single file encompassed by the script's command. If all but one source file has remained unchanged, it will still encompass all files.

Note

Of course, this depends on the parameters passed to the subprocess command. I am generalising here for the sake of providing an example.