#1032593 Automatically map intersphinx references to local packages

#1032593#5
Date:
2023-03-09 17:16:42 UTC
From:
To:
Countless packages contain a stanza in their docs/conf.py like:
    intersphinx_mapping = {
        "python": ("https://docs.python.org/3", None),
    }

Given packages in Debian cannot use network access when being built, and
the dh_make Sphinx boilerplates suggest defining http_proxy to avoid
Sphinx resolving this through the internet, one of these two things
happens:

1) The maintainer patches the upstream source through debian/patches, to
point these references to the local filesystem. That's actually what
src:sphinx does as well for itself, through the intersphinx_local.diff
patch. A quick codesearch[1] reveals ~385 packages doing something
similar.

2) The maintainer does not patch the source, Sphinx attemps to fetch the
file from the network, fails due to http_proxy, and the generated docs
do not resolve these references. Build-time warnings are emitted of the
form:
   WARNING: py:class reference target not found: pathlib.Path
I don't know of an easy way to grep through the build logs to generate
numbers. Anecdotally, I've seen quite a few packages in that category as
well. (Perhaps one could add a tag to the Buildd Log Scanner[2] to scan
for this?)

It'd be great if intersphinx in Debian was patched to map these
references to the local Debian package and also to generate the
necessary dependencies -- perhaps guarded by a environment variable or
command-line option that dh_sphinx would only pass, for example.

Beyond patching the Sphinx code itself, there is of course the matter of
generating these mappings, which is surprisingly non-trivial. From what
I can tell the mappings need to be created heuristically, since I
haven't seen of a way for a central Sphinx to document in metadata where
the generation documentation will be published.

I played around with a few ideas, and while I haven't settled on
something that I feel is not dirty yet, I tried to implement something
akin to what dh-python's pydist/generate_fallback_list.py does: have a
script in the source to be executed manually periodically to regenerate
the cache, which creates a mapping that is then committed to git, and
shipped in the binary.

So I implemented the attached proof of concept that:
* Scans Contents-all and Contents-amd64 to find objects.inv files
  and maps them back to the binary packages;
* Queries UDD (through one query with joins) to:
  - find the respective source packages for these binary packages
  - find the upstream metadata[3] for these source package
* Prints a tab-separated intersphinx_mappings file that has:
  <documentation URL>\t<binary package>\<objects.inv file>

It takes ~10s to run on my computer right now, which should be fine for
being executed periodically by the maintainer.

I'm sure there are many issues with this approach that I haven't thought
through, as well as a number of corner cases, but I wanted to have
something to kickstart this discussion beyond just wishful thinking!

I'd love any feedback! Note that I haven't looked at all at what it
would take to integrate this mapping to the Sphinx source (as well as
${sphinx:Depends}) as I thought it'd be good to validate the approach
before I do so.

Regards,
Faidon

1: https://codesearch.debian.net/search?q=intersphinx_mapping+path%3Adebian%2F&literal=1&perpkg=1
2: https://qa.debian.org/bls/
3: I ran into stale data in that table, which is now tracked as #1032587