Skip to content

Commit

Permalink
Add separate conf.py for the different docs types
Browse files Browse the repository at this point in the history
The goal here is to allow building the `man` pages without all the
additional requirements needed for the `html` docs, so that e.g.
developers packaging `pip` can also package the man pages with minimal
dependencies.

Specifically, this should have no impact on the build output of `nox -s
docs`, but does allow:

    sphinx-build \
        -c docs/man \
        -d docs/build/doctrees/man \
        -b man \
        docs/man \
        docs/build/man

to run with only `sphinx` dependency (in addition to `pip`s
dependencies). While doing this I also used long-opts to `sphinx-build`
in the `noxfile` since I found this more understandable at a glance

An `__init__.py` was added under `docs/man` to satisfy `mypy`. If this
wasn't added it would error:

    docs/man/conf.py: error: Duplicate module named "conf" (also at
    "docs/html/conf.py")
    docs/man/conf.py: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#mapping-file-paths-to-modules for more info
    docs/man/conf.py: note: Common resolutions include: a) using `--exclude` to avoid checking one of them, b) adding `__init__.py` somewhere, c) using `--explicit-package-bases` or adjusting MYPYPATH
    Found 1 error in 1 file (errors prevented further checking)

Adding an extra `__init__.py` under `docs/html` would result in a
separate error since then the `html` module is local (and not the stdlib
one) resulting in a error from `sphinx-build`:

    Extension error:
    Could not import extension sphinx.builders.linkcheck (exception: No module named 'html.parser')

Issue: pypa#12881
  • Loading branch information
matthewhughes934 committed Oct 31, 2024
1 parent 4204359 commit 326638d
Show file tree
Hide file tree
Showing 10 changed files with 112 additions and 76 deletions.
1 change: 1 addition & 0 deletions .readthedocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ sphinx:
python:
install:
- requirements: docs/requirements.txt
- requirements: docs/html/requirements.txt
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ recursive-include src/pip/_vendor *COPYING*

include docs/docutils.conf
include docs/requirements.txt
include docs/html/requirements.txt

exclude .git-blame-ignore-revs
exclude .coveragerc
Expand Down
29 changes: 29 additions & 0 deletions docs/common_conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import os
import re
from typing import Tuple


def read_version() -> Tuple[str, str]:
# Find the version and release information.
# We have a single source of truth for our version number: pip's __init__.py file.
# This next bit of code reads from it.
file_with_version = os.path.join(
os.path.dirname(__file__), "..", "src", "pip", "__init__.py"
)
with open(file_with_version) as f:
for line in f:
m = re.match(r'__version__ = "(.*)"', line)
if m:
__version__ = m.group(1)
# The short X.Y version.
version = ".".join(__version__.split(".")[:2])
# The full version, including alpha/beta/rc tags.
release = __version__
return version, release
return "dev", "dev"


# General information about the project.
project = "pip"
copyright = "The pip developers"
version, release = read_version()
66 changes: 4 additions & 62 deletions docs/html/conf.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
"""Sphinx configuration file for pip's documentation."""

import glob
import os
import pathlib
import re
import sys
from typing import List, Tuple

# Add the docs/ directory to sys.path, because pip_sphinxext.py is there.
# Add the docs/ directory to sys.path to load the common config,
# and pip_sphinxext.py
docs_dir = os.path.dirname(os.path.dirname(__file__))
sys.path.insert(0, docs_dir)

from common_conf import copyright, project, release, version # noqa: E402, F401

# -- General configuration ------------------------------------------------------------

extensions = [
Expand All @@ -28,27 +28,6 @@
"sphinxcontrib.towncrier",
]

# General information about the project.
project = "pip"
copyright = "The pip developers"

# Find the version and release information.
# We have a single source of truth for our version number: pip's __init__.py file.
# This next bit of code reads from it.
file_with_version = os.path.join(docs_dir, "..", "src", "pip", "__init__.py")
with open(file_with_version) as f:
for line in f:
m = re.match(r'__version__ = "(.*)"', line)
if m:
__version__ = m.group(1)
# The short X.Y version.
version = ".".join(__version__.split(".")[:2])
# The full version, including alpha/beta/rc tags.
release = __version__
break
else: # AKA no-break
version = release = "dev"

print("pip version:", version)
print("pip release:", release)

Expand Down Expand Up @@ -95,43 +74,6 @@
html_use_modindex = False
html_use_index = False

# -- Options for Manual Pages ---------------------------------------------------------


# List of manual pages generated
def determine_man_pages() -> List[Tuple[str, str, str, str, int]]:
"""Determine which man pages need to be generated."""

def to_document_name(path: str, base_dir: str) -> str:
"""Convert a provided path to a Sphinx "document name"."""
relative_path = os.path.relpath(path, base_dir)
root, _ = os.path.splitext(relative_path)
return root.replace(os.sep, "/")

# Crawl the entire man/commands/ directory and list every file with appropriate
# name and details.
man_dir = os.path.join(docs_dir, "man")
raw_subcommands = glob.glob(os.path.join(man_dir, "commands/*.rst"))
if not raw_subcommands:
raise FileNotFoundError(
"The individual subcommand manpages could not be found!"
)

retval = [
("index", "pip", "package manager for Python packages", "pip developers", 1),
]
for fname in raw_subcommands:
fname_base = to_document_name(fname, man_dir)
outname = "pip-" + fname_base.split("/")[1]
description = "description of {} command".format(outname.replace("-", " "))

retval.append((fname_base, outname, description, "pip developers", 1))

return retval


man_pages = determine_man_pages()

# -- Options for sphinx_copybutton ----------------------------------------------------

copybutton_prompt_text = r"\$ | C\:\> "
Expand Down
8 changes: 8 additions & 0 deletions docs/html/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# currently incompatible with sphinxcontrib-towncrier
# https://github.com/sphinx-contrib/sphinxcontrib-towncrier/issues/92
towncrier < 24
furo
myst_parser
sphinx-copybutton
sphinx-inline-tabs
sphinxcontrib-towncrier >= 0.2.0a0
Empty file added docs/man/__init__.py
Empty file.
53 changes: 53 additions & 0 deletions docs/man/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import glob
import os
import sys
from typing import List, Tuple

# Add the docs/ directory to sys.path to load the common config
docs_dir = os.path.dirname(os.path.dirname(__file__))
sys.path.insert(0, docs_dir)

from common_conf import copyright, project, release, version # noqa: E402, F401

extensions = [
# our extensions
"pip_sphinxext",
]

print("pip version:", version)
print("pip release:", release)


# List of manual pages generated
def determine_man_pages() -> List[Tuple[str, str, str, str, int]]:
"""Determine which man pages need to be generated."""

def to_document_name(path: str, base_dir: str) -> str:
"""Convert a provided path to a Sphinx "document name"."""
relative_path = os.path.relpath(path, base_dir)
root, _ = os.path.splitext(relative_path)
return root.replace(os.sep, "/")

# Crawl the entire man/commands/ directory and list every file with appropriate
# name and details.
man_dir = os.path.join(docs_dir, "man")
raw_subcommands = glob.glob(os.path.join(man_dir, "commands/*.rst"))
if not raw_subcommands:
raise FileNotFoundError(
"The individual subcommand manpages could not be found!"
)

retval = [
("index", "pip", "package manager for Python packages", "pip developers", 1),
]
for fname in raw_subcommands:
fname_base = to_document_name(fname, man_dir)
outname = "pip-" + fname_base.split("/")[1]
description = "description of {} command".format(outname.replace("-", " "))

retval.append((fname_base, outname, description, "pip developers", 1))

return retval


man_pages = determine_man_pages()
8 changes: 0 additions & 8 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
sphinx ~= 7.0
# currently incompatible with sphinxcontrib-towncrier
# https://github.com/sphinx-contrib/sphinxcontrib-towncrier/issues/92
towncrier < 24
furo
myst_parser
sphinx-copybutton
sphinx-inline-tabs
sphinxcontrib-towncrier >= 0.2.0a0

# `docs.pipext` uses pip's internals to generate documentation. So, we install
# the current directory to make it work.
Expand Down
Empty file.
22 changes: 16 additions & 6 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
}
REQUIREMENTS = {
"docs": "docs/requirements.txt",
"docs-html": "docs/html/requirements.txt",
"tests": "tests/requirements.txt",
"common-wheels": "tests/requirements-common_wheels.txt",
}
Expand Down Expand Up @@ -131,18 +132,21 @@ def docs(session: nox.Session) -> None:
session.install("-r", REQUIREMENTS["docs"])

def get_sphinx_build_command(kind: str) -> List[str]:
# Having the conf.py in the docs/html is weird but needed because we
req_file = REQUIREMENTS.get(f"docs-{kind}")
if req_file is not None:
session.install("--requirement", req_file)
# Having the conf.py in the docs/{html,man} is weird but needed because we
# can not use a different configuration directory vs source directory
# on RTD currently. So, we'll pass "-c docs/html" here.
# See https://github.com/rtfd/readthedocs.org/issues/1543.
# fmt: off
return [
"sphinx-build",
"--keep-going",
"-W",
"-c", "docs/html", # see note above
"-d", "docs/build/doctrees/" + kind,
"-b", kind,
"--fail-on-warning",
"--conf-dir", "docs/" + kind, # see note above
"--doctree-dir", "docs/build/doctrees/" + kind,
"--builder", kind,
"docs/" + kind,
"docs/build/" + kind,
]
Expand All @@ -155,7 +159,13 @@ def get_sphinx_build_command(kind: str) -> List[str]:
@nox.session(name="docs-live")
def docs_live(session: nox.Session) -> None:
session.install("-e", ".")
session.install("-r", REQUIREMENTS["docs"], "sphinx-autobuild")
session.install(
"-r",
REQUIREMENTS["docs"],
"-r",
REQUIREMENTS["docs-html"],
"sphinx-autobuild",
)

session.run(
"sphinx-autobuild",
Expand Down

0 comments on commit 326638d

Please sign in to comment.