feat(diagnose): report a code object manager that is not the runtime's own - #385
volen-silo wants to merge 1 commit into
Conversation
7d0af16 to
9452104
Compare
87cbcac to
a1968d9
Compare
9452104 to
4e5fe59
Compare
a1968d9 to
22e9b4a
Compare
4e5fe59 to
6f617c8
Compare
22e9b4a to
5c39eee
Compare
6f617c8 to
09476f8
Compare
5c39eee to
f1a1b9b
Compare
jussielo-amd
left a comment
There was a problem hiding this comment.
This is a solid feature (surfacing comgr/HIP runtime mismatches), but I found a few issues that affect correctness of the diagnosis output and the fix advice text, plus a couple of secondary concerns. Left inline comments on the ones anchored to this diff; one test-coverage gap below since it points at a file this PR doesn't touch.
Untested field, tests/e2e-cucumber/tests/e2e/examine_steps.rs (not modified by this PR): the e2e assertion for comgr_paths entries still only requires ["path", "real_path", "version", "source"] and wasn't extended to require the new install_root field this PR adds to the wire format, and no e2e step checks hip_paths/hip_selected either. Right now only in-process unit tests exercise install_root — the actual CLI --json output for the field this design turns on is unverified end-to-end.
Requesting changes mainly for the fix-18 advice/data mismatch (correctness bug in user-facing guidance) and the comgr_matches_runtime vs diagnose disagreement — both are logic errors, not style nits.
| "# Then pick ONE of the following. They are alternatives, not steps.", | ||
| "# (a) Keep the system installation: remove or uninstall the wheel that", | ||
| "# supplies the second copy.", | ||
| "# (b) Keep the wheel: order the search path so the runtime's own copy", |
There was a problem hiding this comment.
Option (b) here says "Keep the wheel: order the search path so the runtime's own copy is found first", followed by an LD_LIBRARY_PATH command — but per this PR's own conflict definition (diagnose.rs's matching), "the runtime's own copy" is the copy belonging to the currently-active HIP runtime, which in the canonical wheel/system conflict is the system install, not the wheel. A user who wants to keep the wheel and follows this advice literally will export LD_LIBRARY_PATH to the system comgr directory instead — the opposite of the stated intent — and there's no obvious symptom afterward besides now picking up the system's libraries. Worth double-checking which path this recipe actually resolves to for option (b) and fixing the wording/command so it points at the wheel's own comgr, not the runtime's.
| // `None` rather than `false` when either side is missing: "they disagree" and | ||
| // "there was nothing to compare" are different answers, and a caller that | ||
| // cannot tell them apart reports a conflict on a machine with no ROCm at all. | ||
| e.comgr_matches_runtime = match (comgr.first(), hip.first()) { |
There was a problem hiding this comment.
The documented --json field comgr_matches_runtime is computed with a weaker rule (comgr.install_root != hip.install_root, both non-empty) than check_18_comgr_conflict, which additionally requires the active runtime to ship a matching comgr copy of its own before firing. When the runtime ships no comgr copy of its own — the exact case this PR's own test a_runtime_with_no_copy_of_its_own_is_not_a_conflict covers — rocm examine --json will report comgr_matches_runtime: false while rocm diagnose reports nothing at all. Anyone consuming this field programmatically (or a user comparing the two commands) sees a contradiction. Suggest computing this field with the same logic check_18_comgr_conflict uses.
| crate::collect_sdk_library_paths(&root, &mut paths); | ||
| for path in paths { | ||
| add(path, "managed-runtime"); | ||
| crate::collect_sdk_library_paths(root, &mut paths); |
There was a problem hiding this comment.
install_library_dirs now calls collect_sdk_library_paths (which also scans root/bin and root/lib/rocm_sysdeps/lib) unconditionally for every root, whereas the code this replaces (comgr_search_dirs) only added root/lib/root/lib64 for rocm-install-sourced roots. This silently widens what's probed for plain bare-metal /opt/rocm installs — e.g. a vendored dependency under lib/rocm_sysdeps/lib or bin that happens to match the comgr/hip library name pattern would now be picked up as an extra copy that never showed up before this PR, changing comgr_selected/comgr_paths ordering and copy counts on existing installs. This behavior change to an established field isn't called out in the PR description — was the widened scan for bare-metal roots intentional?
| let mut copies: Vec<LibraryCopy> = Vec::new(); | ||
| let mut seen: std::collections::BTreeSet<String> = std::collections::BTreeSet::new(); | ||
|
|
||
| // Order is the whole point: this is the order the loader consults, so the |
There was a problem hiding this comment.
find_library_copies recomputes install_library_dirs(roots) and re-invokes paths_in_loader_cache (which spawns ldconfig -p) once per prefix, so both now run twice per rocm diagnose/rocm examine call — once for comgr, once for hip — even though neither depends on which prefix is being searched. That's an extra ldconfig -p subprocess plus a second full walk of every managed runtime's lib/lib64 → pythonX/site-packages → _rocm_sdk_* directories on every invocation of a command that's typically run repeatedly while debugging. Might be worth hoisting the roots/loader-cache computation out so it's shared between the two calls.
| .into_iter() | ||
| .map(|root| (root, "managed-runtime")), | ||
| ); | ||
| roots.dedup_by(|a, b| a.0 == b.0); |
There was a problem hiding this comment.
known_install_roots dedups with Vec::dedup_by, which only removes adjacent duplicates. e.rocm_path is pushed first and the sorted /opt/* siblings are appended after, so if e.rocm_path equals one of those sorted entries but isn't adjacent to it in the final vector (e.g. e.rocm_path is /opt/rocm-6.4 and /opt also contains rocm and rocm-6.4, sorting to [rocm, rocm-6.4]), the roots vector ends up [rocm-6.4, rocm, rocm-6.4, ...] and the two rocm-6.4 entries survive the dedup. install_library_dirs/find_library_copies then scan that install's directories twice per probe — masked from the final output only because record_library_copy separately dedups by resolved real path downstream. Sorting the full vector (or using a HashSet) before dedup would close this.
f1a1b9b to
c591f8a
Compare
d9b0a75 to
f2bd0c9
Compare
c591f8a to
2a77f96
Compare
6ea3a21 to
ee48870
Compare
2a77f96 to
c6cfd42
Compare
ee48870 to
a8749a3
Compare
991f5d8 to
166d9ed
Compare
r0x0r
left a comment
There was a problem hiding this comment.
What the change does. Teaches examine/diagnose to notice that the libamd_comgr copy the loader would pick belongs to a different installation than the libamdhip64 runtime, reports the HIP copies alongside the comgr ones, and adds fix-18-comgr-conflict as an advisory-only entry that states both remedies without ranking them.
Coverage. Full read of crates/rocm-core/src/{examine,diagnose,fix,lib}.rs and the four e2e-cucumber files at head, reviewed against the PR's own base (feat/report-comgr-copies), so #384's changes are excluded. Checked against AGENTS.md §3. Both findings from the earlier, now-dismissed review were re-tested specifically. No tests run.
Assessment: looks sound — no blocking findings survived verification.
The earlier review's two findings are addressed
-
fix-18 advice/data mismatch — fixed. The machine-specific
Fixindiagnose.rsderives itsLD_LIBRARY_PATHdirectory frommatching.real_path, wherematchingis the comgr copy whoseinstall_rootequals the HIP runtime's, so "order the search path so the runtime's own copy is found first" now describes what the command actually does. The<directory of the wheel's own copy>placeholder that remains infix.rsis the generic catalog recipe, which has no live paths to fill in — correct for its role. -
comgr_matches_runtimevsdiagnosedisagreeing — fixed.probe_comgrandcheck_18_comgr_conflictnow call the sameexamine::comgr_matches_runtime, andcomgr_matches_runtime_never_disagrees_with_the_diagnosispins that invariant across the catalog cases, including the one that used to diverge (a runtime shipping no comgr copy of its own). Making the disagreement structurally impossible, rather than fixing the two sites to agree, is the right shape of fix. -
The e2e gap is closed too.
assert_comgr_copies_reportednow requiresinstall_root:for field in ["path", "real_path", "version", "source", "install_root"] {
and two new steps assert
hip_pathsandhip_selected, wired into examine-16.
AGENTS.md §3 is satisfied: diagnose-21 (@id:diagnose-fix-comgr-conflict-is-advisory-only) covers the user-observable half, and its comment explains honestly why the conflict itself cannot be provoked in the suite — the detection rule is left to unit tests that build machine state directly. That is the explanation §3 asks for rather than a silent omission.
Non-blocking
managed_runtime_roots()runs twice perfind_library_copies.known_install_roots(examine.rs:2170) calls it to buildroots, theninstall_library_dirs(:2200) calls it again to buildmanaged_library_paths, andprobe_comgrruns the pair twice (comgr, then HIP) — four registry walks per examination. It is a deterministic filesystem read, so this is cost rather than correctness, but threading the first result through would remove both the cost and any question about the two views agreeing.comgr_version_from_file_nameis now called for HIP paths too (record_library_copy). The soname parsing is correct for both; only the name still says comgr.assert_hip_selection_is_statedpasses trivially on the mock lane — withhip_paths == []it only assertship_selectedis null, so the list/selection agreement is never exercised off a GPU lane. It inherits this from the comgr sibling rather than introducing it.assert_fix_is_advisoryusescontains("print-only") || contains("will NOT run it"), so dropping either string still passes.assert_both_options_unrankedcovers the dangerous case, so this is slack rather than a hole.- Untested: the ordering contract in
find_library_copies(LD_LIBRARY_PATH beats loader cache beats install dirs) is the whole point of the function and has no unit test; neither does the partial-version evidence branch indiagnose.rswhere exactly one side has a version string.
Tradeoffs
comgr_matches_runtimereturnsNoneboth for "no known installation claims this copy" and for "the runtime ships no comgr of its own". Serialized, a consumer cannot tell those apart from "not computed". The comments document the distinction; the wire format does not carry it.- The symmetric rule ("do both come from the same installation?") stays quiet on a machine holding only the mismatched comgr, because there would be no alternative to point at. Deliberate and documented, but it does mean a genuinely broken single-stack install reports nothing.
Positive signals
- Driving the JSON field and the diagnostic from one function, then testing that they agree, removes a whole class of future drift.
- Longest-prefix attribution in
owning_install_roothandles managed runtimes spread across_rocm_sdk_*subdirectories without special cases, anddedup_roots_keeping_firsthas a test for the non-adjacent duplicate that a naive consecutive dedup would miss. auto_applicable: false, with both remedies stated and neither recommended, is the right posture for a fix where either choice can break a working Python environment.#[serde(default)]on every new field keeps older consumers reading the new output.
One thing outside the diff
This PR's base is #384, which currently has changes requested and 6 failing checks, so #385 cannot land ahead of it regardless of its own state. Its own checks are green at this head apart from one still running.
The merge decision is yours; this review is posted as a comment and files no approval or change request.
🤖 by agent-hub on AMD AgentHub
166d9ed to
1837ea8
Compare
a8749a3 to
51a20ff
Compare
1837ea8 to
4236280
Compare
51a20ff to
c5c15f7
Compare
4236280 to
19e0fda
Compare
c5c15f7 to
a6d7af6
Compare
juhovainio
left a comment
There was a problem hiding this comment.
I reviewed this PR and found one real gap plus a handful of smaller things, left inline below.
The gap: on a managed runtime installed with the devel extra, the new check can never fire, and comgr_matches_runtime comes back null rather than true on a healthy install. This PR fixes the discovery half of that problem, install_library_dirs now prefers the probe's recorded library_paths over re-deriving the layout, but the attribution half still compares those paths against a root that is the _rocm_sdk_devel directory, which they are not under. The unit tests do not catch it because their fixtures are built from a venv root that production never supplies.
The rest are documentation and description accuracy: two doc comments have drifted onto the wrong functions, docs/wsl.md now contradicts the registration table, and the description's CI section describes work that moved to #384.
One concern I chased and dropped: the claim that the venv-layout fallback had disappeared from examine's search. It has not, library_paths has been in the probe script since the initial import.
a6d7af6 to
e33f8ee
Compare
19e0fda to
ec761a5
Compare
ba1d7fb to
53b13d2
Compare
ec761a5 to
3dfb896
Compare
All five items addressed: attribution fixed from prefix-match to an exact directory-owner map (dir_owners), with the regression fixture rebuilt from the production sibling-directory layout and confirmed by mutation testing (reverted to the old prefix match, watched the test go red, reverted back, confirmed green and no residual diff); both doc-comment splices fixed to sit on their own functions; docs/wsl.md updated so it no longer contradicts the fix-18 registration; PR description corrected for examine-19 naming and CI attribution to #384; and the fix.rs/diagnose.rs remedy text now shares one constant, structurally removing the drift the e2e pin could miss. Full verification bar is clean: cargo fmt --all -- --check, cargo clippy --workspace --all-targets -- -D warnings, cargo clippy -p e2e-cucumber --test e2e -- -D warnings, cargo test --workspace --all-targets (45 binaries, 0 failures), and cargo xtask e2e -- -n skill (6 scenarios, 22 steps, 0 failures).
3dfb896 to
da83dcf
Compare
juhovainio
left a comment
There was a problem hiding this comment.
Re-reviewed after the fixes for the last round — all 5 previously-flagged issues are genuinely fixed, including the owning_install_root attribution bug (this now does exact-directory attribution via dir_owners, not just discovery).
One new issue in the comgr/HIP generalization: probe_comgr documents the comgr and HIP searches as symmetric ("searched the same way, and for the same reason"), but the "N copies found; X would load" note is only emitted for comgr. The HIP branch only checks hip.is_empty() and never calls multi_copy_note for it, so a machine with multiple libamdhip64 copies gets no note, even though the equivalent comgr case does. Left as an inline comment below.
53b13d2 to
13ed5d4
Compare
…s own Builds on the copy search: with every copy of libamd_comgr known, the catalog can say when the one that loads belongs to a different installation than the HIP runtime that loads, and device code compilation therefore fails with an error naming neither. The rule is symmetric, and that is what makes it safe. The design note for this entry proposed a special case -- "when the active runtime is the managed runtime, the matching copy is the wheel copy" -- without which it "fires on every healthy CLI installation". That is a patch over a rule stated asymmetrically. Asking one question of both libraries instead, does the libamd_comgr that would load come from the same installation as the libamdhip64 that would load, makes every case fall out of the rule: a healthy managed install takes both from the managed runtime, so nothing differs and nothing is reported. A control that has to be written as an exception is a rule that has not been stated correctly yet. That turns on attributing a copy to its installation correctly. A managed runtime spreads its libraries across separate _rocm_sdk_* packages that sit beside each other under one site-packages, not nested under the runtime's own root at all, so ownership is decided by looking a found file's directory up against the same directories install_library_dirs already recorded for each root -- never by walking up from the file, which would call each package its own install and fire on the most common install we ship. That is the property the mutation check targets. Two further guards on firing: an unattributed copy is a gap in what the search knows rather than a finding about the machine, and the runtime must actually ship a copy of its own, or there is nothing to point the user at. The entry is print-only and ranks neither remedy. Removing a stack and reordering the search path can each break a working Python environment, and which is right depends on which stack the user means to keep. The finding says out loud that it describes the environment outside the managed runtimes. `rocm serve` puts a managed runtime's libraries first on purpose and gets a different, correct answer, and a report that did not name the environment it examined would read as a claim about one it never looked at. The rule above is now stated exactly once, as `comgr_matches_runtime` in examine.rs, and both this entry and `rocm examine --json`'s field of the same name call it rather than each restating the conditions -- so the two surfaces cannot disagree about one machine, and a regression test pins that. Fix option (b)'s advice matched an earlier, asymmetric statement of the rule; it now says to prefer the wheel's own copy of both libraries, which is what the symmetric rule actually asks the user to do. The machine-readable inspection now carries the same `install_root` attribution for the HIP runtime side that it already carried for the code object manager side, since the conflict question is about both libraries and a reader could not previously check either the attribution or the selection for one of them. Known roots are deduplicated across the whole list, not only where repeats happen to land next to each other. `dedup_by` drops consecutive duplicates, and these roots arrive from three independent sources -- the active install, a sorted /opt scan, and the managed runtimes -- so the active install colliding with a sibling was caught or missed depending on where the path sorted. A root recorded twice can make a machine holding one stack read as a machine holding two, which is the false conflict this entry exists not to report. hip_paths and hip_selected are `serde(default)` for the same reason the comgr fields are: an examination read back over the remote path may have been produced by an older CLI that never wrote them. Once a managed runtime is found, its root resolves to its `_rocm_sdk_devel` package directory, and the search used to guess a venv layout (`root/lib/<python>/site-packages`) to find its sibling packages; that guess does not hold for a real install, so the search now reads back the library directories the SDK probe already recorded (`library_paths`) instead of re-deriving them. This generalises the walker introduced in #384 (`find_library_copies`/`install_library_dirs`) so it serves the HIP side too, which is why `managed_runtime_roots` now returns `library_paths` plural rather than a single `site_packages` guess. The AppPaths::discover data-dir fix and the @requires-os:linux platform tags this commit originally carried for examine-17/18 belong with the scenario they correct, which #384 introduced -- moved there so the parent doesn't depend on the child to pass its own CI, and this commit now only rebases on top of that fix rather than restating it. Sweeping the same gap once it was visible: examine-17 (the host-independent code-object-manager scenario, renumbered by #384's rebase) asserts `hip_paths`/`hip_selected` the same way it asserts `comgr_paths`/`comgr_selected`, and the empty branch had the identical problem #384's review flagged for the comgr side -- `hip_paths: []` / `hip_selected: null` also hold by nothing more than `Examination`'s own defaults, so the assertion could not tell "probed, found none" from "never probed". `probe_comgr` now pushes a "no libamdhip64 found" note the same way it already did for libamd_comgr, and the HIP assertion now requires it in the empty branch. Verified by temporarily suppressing the note and watching the scenario go red, then restoring it. Review: the attribution half still did not survive a realistic managed runtime. `known_install_roots` registers a managed runtime by `candidate.root_path` alone -- `_rocm_sdk_devel` when the `devel` extra is installed -- and `libamd_comgr`/`libamdhip64` live in the sibling `_rocm_sdk_core` package, which sits *beside* `root_path`, not under it. A string-prefix match against the bare root therefore attributed nothing on exactly this shape: both libraries came back `install_root: ""`, `comgr_matches_runtime` hit its empty-root guard and returned `None` instead of `true`, and `check_18_comgr_conflict` could not fire for that runtime at all. The two unit tests meant to catch this (`a_library_deep_inside_a_managed_runtime_belongs_to_the_runtime`, `a_healthy_managed_installation_raises_no_report`) built their fixtures from a venv root (`/data/runtimes/therock/default`, `.../lib/python3.12/site-packages/_rocm_sdk_core/...`) that production never produces, so they passed regardless. Fixed at the root: `install_library_dirs` already walks, for every known root, exactly the directories that root's copies live in (that is the discovery-side fix this commit already made). `find_library_copies` now builds a directory -> owning-root map from that same walk and attributes each found file by looking its directory up in it, rather than by string-prefix match against the bare root. A sibling package's directory is simply one more entry in the map, keyed to the runtime that recorded it, so it attributes correctly without being nested under anything. `owning_install_root` and `record_library_copy` take that map instead of the bare root list; `the_nearest_enclosing_installation_claims_a_library` is removed because the "longest prefix wins" ambiguity it guarded against cannot arise under exact directory lookup. Both fixture tests above were rebuilt from the real installer shape (sibling `_rocm_sdk_*` packages under one `site-packages`, matching `apps/rocm/src/therock.rs`'s `ROCM_SDK_PROBE_SCRIPT`), and `a_library_deep_inside_a_managed_runtime_belongs_to_the_runtime` was confirmed to fail against the old string-prefix attribution before passing against the fix. `find_library_copies` also gained the `active_runtime_dirs` parameter runtime's own library directories are now searched first, ahead of the ambient `LD_LIBRARY_PATH`, for both comgr and HIP, since `rocm serve` prepends them for both libraries equally. The loader cache and the directory walk are now gathered once in `probe_comgr` and passed to both the comgr and HIP searches, rather than each search reading `LD_LIBRARY_PATH`, spawning `ldconfig -p`, and walking every managed runtime's directories again on every invocation -- neither depends on which library is being searched for, so the duplication cost an extra subprocess and directory walk on every `rocm examine`/`rocm diagnose` for no reason. `multi_copy_note` is split out as its own pure function so the "N copies ... would load" note stays independently testable now that `find_library_copies` only returns copies. Two doc-comment splices, both from a missing blank `///` line between two unrelated items: `check_18_comgr_conflict`'s doc paragraph had absorbed check_17's above it, leaving check_17 undocumented; the same shape left `known_install_roots` undocumented under a paragraph describing `dedup_roots_keeping_first`. Both are split back onto the function they actually describe. `docs/wsl.md` said `fix-19-shm-too-small` was the only non-WSL entry that applies on WSL; registering `check_18_comgr_conflict` for `["linux", "wsl"]` made that false the moment this entry existed. Updated to name both. The "Neither option is recommended..." sentence was stated independently in `fix.rs`'s static recipe and `diagnose.rs`'s dynamically-generated one, in slightly different words -- already drifted, and only the `fix.rs` copy was pinned by an e2e assertion, so the `diagnose.rs` copy could drift further with nothing to catch it. Both now read `fix::COMGR_CONFLICT_NEITHER_OPTION_RECOMMENDED`, so they cannot disagree. Not changed: `fix.rs`'s option (b) wording and `comgr_matches_runtime`'s parity with `check_18_comgr_conflict` were flagged in an earlier review round and were already correct by the time this round started -- verified against the current code rather than re-fixed. Rebase onto #384 and a second symmetry gap: #384's own review fixed `comgr_selected` picking `copies.first()` regardless of tier -- a `rocm-install`/`managed-runtime`-only hit used to be reported as "the one that would load" purely because it sorted first, when neither source is one the loader consults on its own. Rebasing that fix under this entry's HIP-runtime work surfaced a second instance of the same asymmetry a human reviewer caught directly on this branch: `probe_comgr`'s doc comment claimed comgr and HIP were searched and reported identically, but only comgr ever got a `multi_copy_note` call; the HIP branch only checked `hip.is_empty()` and pushed nothing for the "found, but only in a directory the loader does not consult" case the comgr side had just been fixed to report. Fixed by stating the tier rule once and applying it to both prefixes rather than fixing it twice: `select_loader_copy` replaces `copies.first()` with a search restricted to `LOADER_PATH_SOURCES` (`active-runtime`, `ld-library-path`, `loader-cache` -- the tiers the loader itself consults), and `copies_note` replaces `multi_copy_note` with the three-way note (no copies / more than one with a winner / one-or-more with none selected) that a `rocm-install`-only hit now needs. `probe_comgr` calls both exactly once for comgr and once for HIP, so there is one call site per library rather than one function's logic duplicated by hand into the other's branch, and the doc comment now says what the code does instead of what it used to do before #384's fix landed. Pinned by `a_search_dir_only_hit_is_not_selected_for_either_prefix`, which runs the same assertions for `COMGR_LIB_PREFIX` and `HIP_RUNTIME_LIB_PREFIX` in one test rather than two, so the rule is proven to apply to both rather than merely written to apply to both. The existing `loader_cache_outranks_search_dirs` and `the_active_runtimes_own_copy_outranks_the_ambient_environment`/ `ld_library_path_outranks_loader_cache_and_search_dirs` tests were carried through the rebase onto `find_library_copies`/ `select_loader_copy` rather than dropped, since they pin mutation-tested tier-ordering bugs #384 already fixed once. `comgr_search_dirs_labels_each_tier`, which tested the now-deleted `comgr_search_dirs_in`, is removed as redundant: `install_library_dirs`'s own tier-labelling is already covered by `a_library_deep_inside_a_managed_runtime_belongs_to_the_runtime` and `a_library_no_installation_claims_is_attributed_to_none`. Verified: `cargo fmt --all -- --check`, `cargo clippy --workspace --all-targets -- -D warnings`, `cargo clippy -p e2e-cucumber --test e2e -- -D warnings`, and `cargo test --workspace --all-targets` all pass (one `xtask::workflow_contract` test flaked under full-suite parallel load on a timing assertion against a real `rocm-smi` subprocess call and passed cleanly in isolation and on a full-suite rerun; unrelated to this change). Cross-compiled the whole workspace, tests included, for `x86_64-pc-windows-gnu` under `-D warnings` to confirm no code is left unix-only-reachable and therefore dead on Windows. `cargo xtask e2e -- -n skill` passes all 6 scenarios, since this round also touches `skills/rocm-doctor/reference.md`. Signed-off-by: Eugene Volen <Eugene.Volen@amd.com>
da83dcf to
955608f
Compare
What
#384 made every copy of
libamd_comgrvisible. This adds the diagnosis: the catalog now reports when the copy that would load belongs to a different installation than the HIP runtime that would load — the state in which device code compilation fails with an error naming neither the library nor the second copy.Why the rule is symmetric
The design note for this entry proposed a special case: "when the active HIP runtime is the managed runtime, the matching copy is the wheel copy. Without this rule the entry fires on every healthy CLI installation."
That is a patch over a rule stated asymmetrically. Asking one question of both libraries instead — does the
libamd_comgrthat would load come from the same installation as thelibamdhip64that would load? — makes every case fall out of the rule rather than being legislated:/opt/rocm/opt/rocmA control that has to be written as an exception is a rule that has not been stated correctly yet.
The detail it all turns on
A managed runtime spreads its libraries across separate
_rocm_sdk_*packages that sit beside each other under onesite-packages, not nested under the runtime's own root (_rocm_sdk_devel, when thedevelextra is installed) at all. Ownership is therefore decided by looking a found file's directory up against the same directoriesinstall_library_dirsalready walks for each root — never a string-prefix match against the bare root, which attributed nothing on exactly this shape and never by walking up from the file either, which would call each package its own installation and fire on the most common install we ship.That is not a theoretical concern — an earlier version of this PR used a string-prefix match, which left every managed runtime installed with the
develextra unattributed (comgr_matches_runtime: nullinstead oftrueon a healthy install), and the two unit tests meant to catch it were built from a venv-shaped fixture production never produces, so they passed anyway. See "Review remediation" below.Two further guards on firing
It advises and ranks nothing
Print-only. Removing a stack and reordering the search path can each break a working Python environment, and which is right depends on which stack the user means to keep — a question only they can answer. The finding also states that it describes the environment outside the managed runtimes:
rocm serveputs a managed runtime's libraries first on purpose and gets a different, correct answer, and a report that did not name the environment it examined would read as a claim about one it never looked at.Verification
cargo fmt --all -- --check,cargo clippy --workspace --all-targets -- -D warnings, andcargo clippy -p e2e-cucumber --test e2e -- -D warnings(theharness = falsetarget--all-targetsskips) — all cleancargo test --workspace --all-targets— clean, run twice after the review round below to rule out the one unrelated parallel-execution flake (restarting_a_keyless_public_service_names_the_public_bind_not_the_key_flag, a filesystem path race under full-suite parallelism, confirmed to pass in isolation) as masking a real failureEvery new assertion was verified to fail, not just to pass, including the ones added in the review round below — each was confirmed red against a deliberately broken version of the thing it covers, then restored.
CI
The scenario this PR's own GPU coverage depends on,
examine-finds-the-managed-runtimes-own-compilation-library, isexamine-19in the currentexamine.feature(renumbered again by #384's own rebase onto a newermain; earlier revisions of this PR body called itexamine-17, which is now a different, unrelated scenario). It was introduced in #384, not here — along with theAppPaths::discoverdata-dir fix and the@requires-os:linuxtag that make it pass on every GPU lane including native Windows's absence. Nothing in this PR's own commit touches that scenario or its fixes; it only rebases on top of them.Review remediation
A human review found a real gap in this PR's own commit, beyond the mechanical renumbering above: see "The detail it all turns on" for the attribution bug on managed runtimes installed with the
develextra, and its fix. Also fixed in this round, all in this PR's own commit:///line left one function's docs attached to the next item instead):check_18_comgr_conflicthad absorbedcheck_17's doc paragraph, leavingcheck_17undocumented;known_install_rootswas undocumented the same way, underdedup_roots_keeping_first's docs.docs/wsl.mdsaidfix-19-shm-too-smallwas the only non-WSL entry that applies on WSL; registeringcheck_18_comgr_conflictfor["linux", "wsl"]made that stale. Updated to name both.fix.rs's static recipe anddiagnose.rs's dynamically-generated one, and only thefix.rscopy was pinned by an e2e assertion. Both now read a sharedfix::COMGR_CONFLICT_NEITHER_OPTION_RECOMMENDEDconstant, so they cannot drift apart.find_library_copiesalso picked up the active-managed-runtime search-priority fix feat(examine): report every code object manager library, not just the first #384's own review round added to the comgr-only search (generalized here to cover HIP too), and the loader cache / directory walk are now gathered once perrocm examine/rocm diagnoseinvocation and shared between the comgr and HIP searches rather than duplicated.fix.rs's option (b) wording, andcomgr_matches_runtime's parity withcheck_18_comgr_conflict) were checked against the current code and found already correct — not re-fixed, just verified.Full detail and verification is in the commit message's second
Review:paragraph.Coverage boundary
The suite cannot install a second ROCm stack, so e2e cannot provoke a real conflict. It pins the half that matters if the entry ever stops being advisory — asking for the fix changes nothing and recommends neither option. The detection rule is proven by unit tests that construct the machine state directly, including the two cases that must stay silent.
Risk
Medium, concentrated in one place: a false report on a healthy managed installation. That is the risk the ticket names, and the symmetric rule plus root-based attribution are the two things standing against it, each with a test that fails when broken.
Follow-up
Overlaps #382, which replaces
auto_applicablewith a per-platform class. This branch is off #384, so the new recipe uses the current bool; whichever lands second rebases. If #382 lands first this entry becomesPRINT-ONLYin the new class directly.