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

Exceptions for `docs/{man,html}` was added for the `mypy` `pre-commit`
hook, since otherwise it errors:

    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)

and the alternative of adding `__init__.py` means that under `docs` 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 Aug 6, 2024
1 parent 3259c06 commit accf1cb
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 76 deletions.
3 changes: 2 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ repos:
rev: v1.10.0
hooks:
- id: mypy
exclude: tests/data
# exclude due to errors from duplicate conf.py under docs
exclude: tests/data|docs/html|docs/man
args: ["--pretty", "--show-error-codes"]
additional_dependencies: [
'keyring==24.2.0',
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()
70 changes: 8 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

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

extensions = [
Expand All @@ -28,27 +28,10 @@
"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"

copyright = copyright
project = project
version = version
release = release
print("pip version:", version)
print("pip release:", release)

Expand Down Expand Up @@ -95,43 +78,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
57 changes: 57 additions & 0 deletions docs/man/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
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

extensions = [
# our extensions
"pip_sphinxext",
]

copyright = copyright
project = project
version = version
release = release
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
13 changes: 8 additions & 5 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,18 +131,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 = Path("docs").joinpath(kind, "requirements.txt")
if req_file.exists():
session.install("--requirement", str(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 Down

0 comments on commit accf1cb

Please sign in to comment.