🐛 Use the physical source directory for generated PlantUML diagrams - #1750
Conversation
`sphinxcontrib-plantuml` starts the PlantUML process with `cwd = os.path.join(srcdir, node["incdir"])`, so `incdir` decides where relative `!include` paths are resolved. Sphinx-Needs derived it from the *logical* docname, which does not exist on disk for documents whose source file lives outside `srcdir` — e.g. documents contributed by `sphinx-mounts`. PlantUML then got a non-existent `cwd`, and the resulting `ENOENT` surfaced as the misleading `WARNING: plantuml command '...' cannot be run`. Derive `incdir` from `env.doc2path(docname, base=False)` instead, via a new shared `set_plantuml_paths()` helper used by all four generated-PlantUML sites (needuml/needarch, needflow, needsequence, needgantt). `doc2path(..., base=False)` returns a srcdir-relative path for an ordinary document and the absolute external path for a mounted one; `os.path.join` discards its left operand when the right one is absolute, so both resolve correctly. Ordinary documents keep the byte-identical `incdir` they had before, which also keeps PlantUML's content-addressed cache valid and machine-independent for them. Closes #1749
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1750 +/- ##
==========================================
+ Coverage 86.87% 89.42% +2.54%
==========================================
Files 56 73 +17
Lines 6532 10614 +4082
==========================================
+ Hits 5675 9492 +3817
- Misses 857 1122 +265
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…bundle The showcase bundle's PlantUML `!include` needs useblocks/sphinx-needs#1750, which is unreleased. Pinning at `master` left CI red — master does not carry the fix yet — so the new bundle was never actually exercised in CI. Point at the PR branch instead. This is strictly temporary: the branch is deleted when #1750 merges, so the pin must then move to `master` (if the release is still pending) or straight to the `sphinx-needs>8.3.0` constraint the TODO names.
chrisjsewell
left a comment
There was a problem hiding this comment.
haven't checked through the actual code, but approve in principle
ReviewRight root cause, right API. Deriving 1. [Medium] Two of the four refactored call sites are never exercised (correctness / test coverage)The PR's stated value is that the bug existed at four sites and all four now route through the helper — but the fixture document only contains sphinx-needs/tests/test_plantuml_incdir.py Lines 138 to 145 in b8a5502 I read all four sites and they do pass the right 2. [Medium] Mounted documents get a location-dependent PlantUML cache key (performance)
sphinx-needs/sphinx_needs/diagrams_common.py Lines 133 to 135 in b8a5502 The docstring acknowledges the non-portability but frames it as acceptable because it only affects mounted documents — which are exactly the documents this PR exists to fix. 3. [Low] The mount stand-in stores
|
The mounted fixture document only contained `needuml` and `needflow`, so the `needsequence` and `needgantt` call sites of the new helper were never executed — two of the four sites the fix touches had no regression cover. Add both directives to the bundle document. This needs a little fixture care: - `process_needsequence` discards its PlantUML node and emits "no needs found" unless it can draw at least one connection, and a connection requires a sender -> message -> receiver chain over the traversed link type. The single need is therefore replaced by a three-need chain. - `needgantt` warns for any need without `:duration:`, which the test's zero-warning assertion would flag, so every need carries one. Reverting the `incdir` line now fails with four wrong entries instead of two. Also document why `_docname_to_path` must hold `str` and not `Path`: Sphinx stores `_StrPath` there, and its own HTML builder slices the value to recover the source suffix, so a plain `pathlib.Path` raises `TypeError: 'PosixPath' object is not subscriptable` on Sphinx 7.4.
Follow-up on the review findingsAddressed in e8d0791. 1. [Medium] Untested call sites — fixedThe mounted bundle document now exercises all four directives that route through Two fixture details were needed to get there, both worth recording because they are easy to trip over again:
3. [Low]
|
useblocks/sphinx-needs#1750 merged as e8c7a5aa, and its branch was deleted — so the previous pin at that branch no longer resolves. Point back at `master`, which now carries the fix, and relock (b8a55022 -> e8c7a5aa). This keeps the showcase bundle tested against the real fix ahead of a sphinx-needs release. The pin still wants replacing with `"sphinx-needs>8.3.0"` once that version ships.
Closes #1749
Problem
sphinxcontrib-plantumlstarts the PlantUML process withcwd = os.path.join(srcdir, node["incdir"]), soincdirdecides where relative!includepaths are resolved.Sphinx-Needs derived
incdirfrom the logical docname:For a document whose source file does not physically live under
srcdir— e.g.one contributed by sphinx-mounts,
which registers an absolute external path for its docname — that yields a
directory that does not exist.
subprocess.Popenfails withENOENT, whichsphinxcontrib-plantumlreports as:The message is misleading: the PlantUML executable is fine, the
cwdis not.Fix
Derive
incdirfromenv.doc2path(docname, base=False)instead — the samething
sphinxcontrib-plantuml's ownumldirective does — through a new sharedset_plantuml_paths()helper indiagrams_common.py.The bug was present at four sites, all now routed through the helper:
needuml/needarchdirectives/needuml.pyneedflow(plantuml engine)directives/needflow/_plantuml.pyneedsequencedirectives/needsequence.pyneedganttdirectives/needgantt.pyWhy
base=Falseand not an absolute pathdoc2path(..., base=False)returns a srcdir-relative path for an ordinarydocument and the absolute external path for a mounted one. Since
os.path.joindiscards its left operand when the right one is absolute, PlantUMLends up with the correct
cwdin both cases.Picking the relative form matters beyond taste:
incdirto before(
dirname("a/b/c")==dirname("a/b/c.rst")), so nothing changes for existingprojects.
incdirfeedshash_plantuml_node(), i.e. PlantUML's content-addressed cachekey. Always emitting an absolute path would invalidate every cached diagram and
make the cache non-portable across machines and checkout locations. This way
only genuinely-mounted documents get an absolute key.
Test
tests/test_plantuml_incdir.pybuilds a host project plus a sibling bundlemounted via a ~20-line stand-in for sphinx-mounts (it reproduces the one trick
that matters — an absolute path in
Project._docname_to_path— so the testcarries no new dependency), and asserts:
incdir == "sub";"mounted";!include, with zerobuild warnings.
Verified failing before the fix (
['mounted', 'mounted'] != [<bundle>, <bundle>])and passing after.
Full suite: 1018 passed. The two
tests/test_sn_collapse_button.pyfailures onthis branch reproduce identically on
masterand are unrelated.