56  Make for Python (Basic)

56.1 Basic Structure

target: dependencies
    command

Automatic variables:

target: dependency1 dependency2
    # $@ - The target's name
    # $< - The first dependency's name
    # $^ - Names of all dependencies
    # $* - The stem (part matched by %) in pattern rules
    $(CC) -o $@ $^

56.2 Makefile for Python

Let’s start with a simple example of a Python project structure:

my_project/
├── src/
│   ├── data_processor.py
│   └── utils.py
├── tests/
│   └── test_processor.py
├── requirements.txt
└── Makefile

Here’s a basic Makefile to handle common Python project tasks:

# Variables
PYTHON = python3
PIP = pip3
TEST_DIR = tests
SRC_DIR = src

# Default target (runs when you just type 'make')
.DEFAULT_GOAL := help

# Help target - lists available commands
help:
    @echo "Available commands:"
    @echo "  make install    - Install Python dependencies"
    @echo "  make test      - Run tests"
    @echo "  make clean     - Remove Python cache files"
    @echo "  make lint      - Check code style with flake8"

# Install dependencies
install:
    $(PIP) install -r requirements.txt

# Run tests
test:
    $(PYTHON) -m pytest $(TEST_DIR)

# Clean Python cache files
clean:
    find . -type d -name "__pycache__" -exec rm -rf {} +
    find . -type f -name "*.pyc" -delete
    find . -type f -name "*.pyo" -delete
    find . -type f -name ".coverage" -delete

# Lint code
lint:
    flake8 $(SRC_DIR)

# Mark targets that don't create files
.PHONY: help install test clean lint

Let me explain the key concepts:

  1. Rules and Targets: Each block in a Makefile is called a rule. The basic structure is:

    target: dependencies
        commands

    For example, in our test target:

    test:
        $(PYTHON) -m pytest $(TEST_DIR)

    When you run make test, it executes the pytest command.

  2. Variables: We define variables at the top of the Makefile:

    PYTHON = python3
    PIP = pip3

    You can use them with $(VARIABLE_NAME). This makes the Makefile more maintainable.

  3. .PHONY Targets: These are targets that don’t create files. We mark them with .PHONY to tell Make that these targets are always considered “out of date” and should run every time.

  4. Dependencies: Make’s real power comes from handling dependencies. Here’s an example of a more complex rule:

    report.pdf: analysis.py data.csv
        $(PYTHON) analysis.py

    This tells Make that report.pdf depends on analysis.py and data.csv. If either file changes, Make will rebuild report.pdf.

To use the Makefile:

  1. Navigate to your project directory

  2. Run commands like:

    make install  # Install dependencies
    make test    # Run tests
    make clean   # Clean up cache files

Some advanced features you might find useful:

# Run multiple targets in sequence
all: clean install test lint

# Target with automatic variables
%.html: %.md
    pandoc $< -o $@

# Conditional execution
check-env:
    @if [ -z "$(VIRTUAL_ENV)" ]; then \
        echo "Not in a virtual environment!"; \
        exit 1; \
    fi

Common patterns in Python projects:

  1. Virtual Environment Management:

    venv:
        python3 -m venv .venv
        source .venv/bin/activate && pip install -r requirements.txt
  2. Development Setup:

    dev-setup: venv
        pip install -e ".[dev]"
  3. Documentation Generation:

    docs:
        cd docs && make html

Remember these key points:

  • Use tabs (not spaces) for indentation in Makefiles
  • Commands in a rule are executed in a new shell by default
  • Use @ before a command to prevent it from being printed
  • Use - before a command to continue even if it fails

56.3 Variable Summarize

Let me explain the commonly used variables in Makefiles, starting with the basic syntax and building up to more advanced usage.

Basic Variable Definition:

# Simple assignment (immediate evaluation)
PYTHON = python3

# Recursive assignment (lazy evaluation)
TESTS != ls tests/*.py

# Conditional assignment (only if not already set)
PREFIX ?= /usr/local

Here are the most commonly used variables in Makefiles, organized by their purpose:

56.3.1 Program and Tool Variables

# Basic commands
PYTHON = python3
PIP = pip3
RM = rm -f
MKDIR = mkdir -p
CP = cp -r

# Build tools
CC = gcc        # C compiler
CXX = g++       # C++ compiler
AR = ar         # Archive maintainer
MAKE = make     # Make command itself

56.3.2 Directory and Path Variables

# Common directory structure
SRC_DIR = src
BUILD_DIR = build
DIST_DIR = dist
DOC_DIR = docs
TEST_DIR = tests

# Installation paths
PREFIX = /usr/local
BINDIR = $(PREFIX)/bin
LIBDIR = $(PREFIX)/lib

56.3.3 Flags and Options Variables

# Compiler and linker flags
CFLAGS = -Wall -O2          # C compiler flags
CXXFLAGS = -std=c++11       # C++ compiler flags
LDFLAGS = -L/usr/lib        # Linker flags

# Python-specific flags
PYTEST_FLAGS = -v --cov
PIP_FLAGS = --no-cache-dir

56.3.4 Special Make Variables (Automatic Variables)

target: dependency1 dependency2
    # $@ - The target's name
    # $< - The first dependency's name
    # $^ - Names of all dependencies
    # $* - The stem (part matched by %) in pattern rules
    $(CC) -o $@ $^

56.3.5 Shell Command Variables

# Get system information
UNAME := $(shell uname)
VERSION := $(shell git describe --tags)
PYTHON_VERSION := $(shell python --version)

# Dynamic file listing
SOURCES := $(shell find $(SRC_DIR) -name '*.py')

56.3.6 Environment Variables Access

# Access environment variables
HOME = $(HOME)
PATH = $(PATH)
VIRTUAL_ENV = $(VIRTUAL_ENV)

56.3.7 Examples

Let’s see a practical example that puts these together:

# System detection
UNAME := $(shell uname)

# Set system-specific variables
ifeq ($(UNAME), Darwin)
    PYTHON = python3
    BROWSER = open
else
    PYTHON = python3
    BROWSER = python -mwebbrowser
endif

# Project structure
PROJECT = myproject
SRC_DIR = src
TEST_DIR = tests
BUILD_DIR = build
DIST_DIR = dist

# Python settings
VENV = .venv
PIP = $(VENV)/bin/pip
PYTEST = $(VENV)/bin/pytest
PYTEST_FLAGS = -v --cov=$(PROJECT)

# Targets using variables
test:
    $(PYTEST) $(PYTEST_FLAGS) $(TEST_DIR)

build: $(BUILD_DIR)
    $(PYTHON) setup.py build

$(BUILD_DIR):
    $(MKDIR) $@

Important things to remember about Makefile variables:

  1. Variable Expansion: Variables are expanded when used, not when defined (unless := is used)

    # Immediate expansion
    FILES := $(wildcard *.txt)
    
    # Delayed expansion
    FILES = $(wildcard *.txt)
  2. Environment Variables: Make automatically imports all environment variables

    # Can use environment variables directly
    USER_HOME = $(HOME)
  3. Override from Command Line: Variables can be overridden when running make

    make PYTHON=python3.9 test