Intersphinx¶
The [intersphinx] section configures cross-project references
against external Sphinx inventories (objects.inv files),
mirroring Sphinx’s intersphinx extension.
Each configured project makes its documented objects
(labels, terms, functions, and so on) resolvable from your own project.
Minimal example¶
[intersphinx]
cache_days = 5 # 0 = always refresh, negative = never expire
timeout = 30.0 # seconds per HTTP request
disabled_reftypes = ["std:doc"] # "*" and "domain:*" forms supported
offline = false # never fetch; serve cache or fail soft
[intersphinx.projects.python]
url = "https://docs.python.org/3"
# optional; defaults to ["<url>/objects.inv"]; entries are URLs or paths
# relative to the ubproject.toml directory; tried in order (Sphinx parity)
inventories = ["https://docs.python.org/3/objects.inv"]
[intersphinx.projects.sphinx]
url = "https://www.sphinx-doc.org/en/master"
How references reach the inventories¶
Mirroring Sphinx, a reference consults the configured inventories in three ways:
Automatic fallback: a plain
:ref:,:term:or:doc:whose target is not found in your own project is looked up across all configured inventories, unless its type is listed indisabled_reftypes(:doc:is excluded by default, exactly as in Sphinx).Inventory-qualified targets: a target written
project:name(for example:ref:`python:comparisons`) looks upnamein that one project’s inventory — and deliberately ignoresdisabled_reftypes, because writing the prefix is itself the opt-in.Explicit roles: the
:external:role family (:external:ref:`…`,:external+python:py:func:`…`) always targets the inventories and also ignores the list.
The automatic fallback means a typo in a :ref: target can,
in principle, silently match an identically-named object
in an external project — the same exposure a Sphinx build has.
A dangling reference that matches nowhere warns
(std.ref / std.doc / std.term),
so only an accidental name collision resolves silently.
To opt out of the automatic behaviour entirely,
set disabled_reftypes = ["*"] —
qualified targets and explicit roles keep working,
so every external link is then a visible, deliberate one.
Options¶
- cache_days
Type:
integer(default:5)Inventory cache lifetime in days, matching Sphinx’s
intersphinx_cache_limitsemantics: a positive value expires a cached inventory after that many days (after which it is revalidated with a conditional request),0always refreshes, and a negative value never expires the cache.- timeout
Type:
number(default:30.0)Timeout, in seconds, for each inventory HTTP request.
- disabled_reftypes
Type:
array of strings(default:["std:doc"])Reference types excluded from the automatic (un-qualified) fallback, such as
"std:doc". The"*"(all) and"domain:*"(whole-domain) wildcard forms are supported. Inventory-qualified targets and explicit:external:roles ignore this list.Entries name the inventory object type:
"std:label"for:ref:,"std:term"for:term:, and"std:doc"for:doc:. Sphinx’sintersphinx_disabled_reftypesnames:ref:as"std:ref"; ubcode accepts that spelling as an alias of"std:label", so a list copied fromconf.pybehaves identically.- offline
Type:
boolean(default:false)When
true, never perform network fetches: inventories are served from the cache (stale is acceptable) or the reference fails soft with a diagnostic (references never fail the build).cache_daysthen only affects labelling — an expired-but-present cached inventory is served as stale (an informational diagnostic) rather than as a silent cache hit.- resolve_self
Type:
string(default:"")The name under which this project refers to itself, mirroring Sphinx’s
intersphinx_resolve_self. A reference whose inventory qualifier equals this name resolves locally (the qualifier is stripped and the project’s own targets are consulted) instead of against an external inventory — for example, withresolve_self = "myproj", both a:ref:targetingmyproj:some-labeland the explicit:external+myproj:ref:role form resolve locally. Empty (the default) disables the behaviour. The self name takes precedence over a configured project of the same name, so avoid reusing an entry from[intersphinx.projects]here — that project would become unreachable via qualified references.- projects
Type:
table(default: empty)A table of external projects keyed by name (
[intersphinx.projects.<name>]). The name is the prefix used by inventory-qualified lookups. Each project accepts:- url
Type:
string(required)Base URL of the external project’s documentation. Generated links are relative to this URL, and it must be unique across all configured projects.
- inventories
Type:
array of strings(default: derived fromurl)Ordered inventory locations to try. Each entry is either a URL (it contains
"://") or a filesystem path relative to theubproject.tomldirectory. When omitted, the single location"<url>/objects.inv"is used.
Checking what the configuration resolves¶
To see exactly which references the configured inventories make available, list them with the CLI:
ubc intersphinx list # every configured project
ubc intersphinx list python # only [intersphinx.projects.python]
ubc intersphinx list -d py -o function # filter by domain / object type
ubc intersphinx list -f json # machine-readable output
The command resolves the nearest ubproject.toml
(use -p/--project to point it elsewhere),
loads each project’s inventory through the shared inventory cache
— honouring cache_days, timeout, and offline —
and prints one block per project without indexing the project.
A project whose inventory cannot be loaded is reported on standard error and skipped,
the remaining projects are still listed,
and the command exits non-zero.
To inspect a single objects.inv file or URL
without any project configuration,
use ubc intersphinx inspect instead.
To search the loaded inventories together with the project’s own
labels, glossary terms, documents and filed index entries —
ranked, with the exact :external: role to paste —
use ubc query search (see Finding what you can reference):
ubc query search --scope python array # only [intersphinx.projects.python]
ubc query search --scope external array # any loaded inventory
ubc query search --kind py:function # by object type
Unlike ubc intersphinx list, it indexes the project first,
so it can answer for local and external targets in one ranked result,
and a project whose inventory failed to load is reported on standard error
(and in the inventories array of -f json)
so an absent symbol is not mistaken for an absent inventory.
Comparing two inventories¶
ubc intersphinx diff compares two inventories and reports what changed:
ubc intersphinx diff old/objects.inv new/objects.inv
ubc intersphinx diff -d std a.inv b.inv # only the std domain
ubc intersphinx diff -f json a.inv b.inv # machine-readable output
ubc intersphinx diff https://example.com/docs/ ./site/objects.inv
Each argument is a local path or an HTTP(S) URL,
resolved exactly as inspect resolves its own —
URLs go through the shared inventory cache,
so --refresh and --offline mean the same thing here.
The -d / -o / -n filters apply to both inventories before comparing,
so a scoped diff answers “are these equal within this scope?”.
A filter that matches nothing on either side compares two empty sets,
which are equal — the command reports 0 identical,
and exits 0 when the two files’ headers also agree.
A header difference is still reported through any filter,
because the header describes the file rather than the filtered view.
When a diff is used as a CI gate, check the filter spelling:
against two files that share a header, a mistyped -d
greens the gate rather than failing it.
The report names the changed column, which is what distinguishes a moved anchor from a re-worded title:
header
~ project ubCode -> (blank)
std:label
~ intro dispname Getting started -> Getting Started
std:term
~ RST uri glossary.html#term-RST -> glossary.html#term-rst
summary: 0 removed, 0 added, 2 changed (1 uri, 1 dispname, 0 priority); 301 identical
Two inventories are compared the way every reader reads them —
keyed on (domain:type, name), which is how Sphinx’s own loader builds its map.
Two things therefore do not count as differences.
Row order is one: producers sort differently
(Sphinx orders by name within a domain, interleaving documents, labels and terms),
and no consumer can observe the difference,
so two inventories holding the same rows in a different order compare as identical.
The $ anchor abbreviation is the other:
it is expanded on load, so index.html#$ and index.html#my-label
are the same URI for the entry my-label.
A - display name — and one that simply repeats the entry name — are likewise one state.
Exit codes make the command usable as a CI gate:
Code |
Meaning |
|---|---|
|
The two inventories are identical (header included). |
|
Differences were found. The report is on standard output. |
|
No comparison was delivered: an inventory could not be read or parsed, or the report itself could not be written (a closed pipe, a full disk). |
Pass --exit-zero to report differences without failing the build;
it changes the exit status only, never the report.
It suppresses 1 and deliberately not 2 —
a run that could not produce a report has not shown that the inventories agree.
Network-restricted CI¶
Because inventory fetches happen over the network,
CI environments without outbound access
should either set offline = true
with committed local inventories files
(the Doxygen-tagfile mode),
or pre-warm the inventory cache before the run.
Fetch failures are reported as warnings and never fail ubc check,
so cross-project references simply fall back to placeholders
if no inventory is available.