Bridge project and documentation builds¶
The Sphinx Stack can be used as a standalone docs repository, or embedded inside a parent project. This guide demonstrates how to bridge the docs build with a Python project’s main build. Once bridged, project contributors can install, build, and check the docs from the root of the project with the main build system.
Parent projects and the build describes the full benefits of bridging the build in a larger project.
Plan and requirements¶
The bridge is built by making up to three changes to the build.
The bridge shims the docs build targets in the main build. Any build system is
capable of adding targets that call other systems. When shimmed, the docs targets like
html and clean pass through to docs/Makefile, with arguments.
The bridge also merges the virtual environments, removing the need for a separate docs environment. This change is optional but recommended. To combine environments, your project must provide Python 3.11 or higher to the Sphinx Stack. Any Python dependency manager will do, and this guide illustrates with three:
pip 25.1 and higher
uv 0.4.27 and higher
Poetry 1.2.0a2 and higher
After adding the bridge, it’s also possible to adjust the documentation workflows to use your project’s main build. The workflows were designed with Make, so they only work if you use it for your build system.
Example project layout¶
This guide illustrates the bridge through an example project. In the example project, the file tree contains:
Project
│
├── ...
├── docs
│ └── Makefile
├── .venv
├── Makefile
└── pyproject.toml
The example project’s root Makefile has two conventional targets that need
adjustment:
setupfor building the virtual environment and syncing dependenciescleanfor cleaning up the virtual environment and temporary files
Set the build paths¶
Where your main build sets environment variables, redeclare the docs environment variables that specify the build paths:
DOCS_BUILDDIRis the destination for the docs. If you have special distribution needs you can override this, but for most builds this can be left as-is.DOCS_VENVDIRis the virtual environment of the docs. If you’re merging the virtual environments, set this as a relative path from the docs directory to your project’s virtual environment.VALE_DIRis the path to the Vale binary. The full path depends on the location of your virtual environment, so it’s best to copy this as-is.
In the example project, this looks like:
# Env vars for the docs build
export DOCS_BUILDDIR ?= _build
export DOCS_VENVDIR ?= ../.venv
export VALE_DIR ?= $(DOCS_VENVDIR)/lib/python*/site-packages/vale
Integrate the docs setup¶
The next step is to incorporate the docs installation target, and optionally the dependencies, into the main build.
Separate virtual environments¶
If you don’t plan to merge the virtual environments, override the installation target by calling all three doc installation targets in a row.
In the example project, this is written as:
# Override the
.PHONY: docs-install
docs-install:
$(MAKE) -C docs install --no-print-directory
$(MAKE) -C docs vale-install --no-print-directory
$(MAKE) -C docs pymarkdownlnt-install --no-print-directory
Merged virtual environments¶
To merge virtual environments, the setup target must handle both development and
docs packages, and enumerate all docs packages in pyproject.toml. Your project will
also need dependency groups to organize the packages. The result will be one virtual
environment fed by three dependency groups in pyproject.toml:
devfor development buildsdocsfor extra docs packages that your project needsdocs-sphinx-stackfor the core docs packages set by the Sphinx Stack
First, add the dependency groups. The docs group should depend on the Sphinx Stack group:
[dependency-groups]
dev = [
# Packages for main development and testing
]
docs = [
# Packages for extra docs features
{include-group = "docs-sphinx-stack"},
]
docs-sphinx-stack = [
# Core docs packages
]
If your pyproject.toml file didn’t already have a dev dependency group, review the
packages listed in the dependencies key, then move any non-runtime dependencies to
the dev dependency group.
If your project needs extra docs features, like the Mermaid or LaTeX Sphinx extensions,
add their packages to the docs group.
Copy the contents of docs/requirements.txt into the docs-sphinx-stack group.
In the main build, override the docs installation target to install the docs
dependency group, then make the project’s setup target depend on the docs target. In
the example project, it’s written like this:
.PHONY: setup
setup: docs-install
pip install --group dev
# ...
# Override for `install` target in docs project
.PHONY: docs-install
docs-install:
pip install --group docs
$(MAKE) -C docs vale-install --no-print-directory
$(MAKE) -C docs pymarkdownlnt-install --no-print-directory
.PHONY: setup
setup: docs-install
uv sync --group dev
# ...
# Override for `install` target in docs project
.PHONY: docs-install
docs-install:
uv sync --no-dev --group docs
$(MAKE) -C docs vale-install --no-print-directory
$(MAKE) -C docs pymarkdownlnt-install --no-print-directory
.PHONY: setup
setup: docs-install
poetry install --only dev
# ...
# Override for `install` target in docs project
.PHONY: docs-install
docs-install:
poetry install --only docs
$(MAKE) -C docs vale-install --no-print-directory
$(MAKE) -C docs pymarkdownlnt-install --no-print-directory
If your docs aren’t written in Markdown, remove the command that runs the
pymarkdownlnt-install target.
Shim the remaining targets¶
The docs build has many targets, but only a handful of them overlap or collide with most project builds, so we only need to override two more. The rest can pass straight through to the docs build.
In the example project, the main build calls the targets like this:
# Override for `clean` target in docs project. We don't want to touch `.venv`,
# so we pass a temp dir instead.
.PHONY: docs-clean
docs-clean:
DOCS_VENVDIR=$(mktemp) $(MAKE) -C docs clean --no-print-directory
# Override for `help` target
.PHONY: docs-help
docs-help:
@echo "Commands in the documentation subproject:"
$(MAKE) -C docs help --no-print-directory
@echo "Run these commands with 'make docs-<command>' in the project root."
# Shim for the rest of the targets in docs Makefile
.PHONY: docs-%
docs-%: docs-install
$(MAKE) -C docs $(@:docs-%=%) --no-print-directory
Variables and Makefiles
When calling another Makefile with $(MAKE) -C, also known as a sub-Make call,
variables with default values in the child Makefile won’t be overridden. To override
them, you must set them explicitly with export or as as command-line arguments.
For example, within the main build, if you need to customize
SPHINX_AUTOBUILD_OPTS, pass it to the docs build like this:
$(MAKE) -C docs run SPHINX_AUTOBUILD_OPTS="$(SPHINX_AUTOBUILD_OPTS)"
Adjust the Read the Docs build¶
With the Makefile in a different location than usual, and its being a separate process,
it’s simplest to override the Read the Docs build in .readthedocs.yaml to call the
same build targets that developers use locally.
If you use an uncommon system, you might need to install it during the workflow’s
create_environment job.
If you merged the virtual environments, make sure to set
DOCS_VENVDIR=${READTHEDOCS_VIRTUALENV_PATH} in all commands.
Here’s what it looks like in the example project:
build:
os: ubuntu-24.04
tools:
python: "3.12"
jobs:
post_checkout:
- git fetch --tags --unshallow # Also fetch tags
create_environment:
- python3 -m venv "${READTHEDOCS_VIRTUALENV_PATH}"
install:
- make docs-install DOCS_VENVDIR="${READTHEDOCS_VIRTUALENV_PATH}"
build:
html:
- make docs DOCS_VENVDIR="${READTHEDOCS_VIRTUALENV_PATH}" DOCS_BUILDDIR="$READTHEDOCS_OUTPUT/html/"
Adjust the doc workflows¶
If your project uses the Sphinx Stack workflows and Make, adjust the workflows to use the bridged targets.
For the main checks, override the target names and paths through the workflow inputs:
jobs:
documentation-checks:
uses: canonical/documentation-workflows/.github/workflows/documentation-checks.yaml@main
with:
working-directory: "."
fetch-depth: 0
install-target: docs-install
spelling-target: docs-spelling
woke-target: docs-woke
linkcheck-target: docs-linkcheck
pa11y-target: docs-pa11y
If your docs are written in Markdown, override the path and command inputs in the Markdown linter workflow:
- name: Create venv
working-directory: "."
run: make docs-install
- name: Lint markdown
working-directory: "."
run: make docs-lint-md