feat(examine): report every code object manager library, not just the first - #384
volen-silo wants to merge 1 commit into
Conversation
4e5fe59 to
6f617c8
Compare
6f617c8 to
09476f8
Compare
siloteemu
left a comment
There was a problem hiding this comment.
🔴 Automated review · pr-review-watcher · 09476f8
This automation never files a GitHub approval, so no approving review will appear here whatever the outcome — the merge decision stays with a human reviewer.
Summary
Adds a Linux probe that records every libamd_comgr copy on the machine (path, resolved path, version, source), plus four new Examination JSON fields, four unit tests and one Gherkin scenario — outcome: Needs work (one blocking issue). Verified: ran the rocm-core examine test module (41 pass) on a throwaway copy and mutated it per branch — reverting the multi-hit walk, the resolve-path dedup and the digits-only version filter each fails exactly one test, but removing the intra-directory sort(), flipping copies.first() to .last(), and gutting probe_comgr to an immediate return all leave every test green; confirmed examine --json flattens Examination so the new keys really surface, that no other consumer reads the new fields or hip_libs_on_ld_path, that the managed-runtime layout claim matches runtime.rs, and that no comgr probe existed on the base at all (so the title's "not just the first" describes a new field rather than a widened one). Checks at review time: 24 success, 2 failures, 1 skipped, 1 cancelled. Blocking: 1 · Non-blocking: 4.
🚫 Blocking (must fix before merge)
crates/rocm-core/src/examine.rs:1982 — comgr_paths_in_loader_cache gates on which("ldconfig"), and which (same file, ~line 616) walks only the process PATH with no /sbin fallback. This re-introduces a bug the same crate already fixed and documented: crates/rocm-core/src/lib.rs:2543-2560 defines ldconfig_cache() whose doc comment says in as many words that "ldconfig lives in /sbin, which is not on a non-root user's PATH on Debian and derivatives. Looking it up by bare name there yields nothing, and an empty cache is indistinguishable from a cache that does not list the library" — and records the user-visible harm it caused last time (a correctly installed library read as "not registered with the linker"). Its fix is to try ["ldconfig", "/sbin/ldconfig", "/usr/sbin/ldconfig"].
Why it blocks: on those hosts the whole loader-cache tier silently contributes nothing, and that tier is the one that finds the copy the loader actually resolves. The consequences are exactly the ones this feature exists to prevent — a copy registered only in ld.so.cache (a ROCm install outside /opt, or one whose directory is on ld.so.conf but not LD_LIBRARY_PATH) is either missed entirely, producing the "no libamd_comgr found …" note on a machine that has one, or is ranked below a rocm-install/managed-runtime entry so comgr_selected names the wrong copy. It also silently degrades rather than reporting "could not ask", which is the distinction the sibling function was written to preserve. AGENTS.md §5 requires checking sibling implementations before adding a probe.
Fix: drop the which gate and reuse the existing helper — make ldconfig_cache() pub(crate) (the same treatment this PR already applies to collect_sdk_library_paths) and parse its Option<String>, treating None as "could not ask" rather than "no copies". It is timeout-bounded (capture_optional_command → OPTIONAL_COMMAND_TIMEOUT), so this does not trade away the 5s bound the current run(..., SHORT) gives. A smaller variant — mirroring the three-candidate list locally — works too but leaves two descriptions of the same trap to keep in sync.
Non-blocking
crates/rocm-core/src/examine.rs:236,239,1875— "in loader search order" / "the copy the loader would pick" overclaims: therocm-installandmanaged-runtimetiers are not loader search locations at all unless they also appear onLD_LIBRARY_PATH/ld.so.conf, yet the hedge names onlyRUNPATH,ld.so.preloadand container remapping; one sentence saying the last two tiers are evidence of what exists rather than of what loads would make thesourcefield's purpose explicit and keep the future conflict rule honest.crates/rocm-core/src/examine.rs:1889-1917—probe_comgritself has no test: replacing its body with an immediatereturnleaves all 41 examine tests green, and the new scenario passes too (the fields serialise as[]/nullfromDefault), so on every lane without ROCm present the scenario proves only that two keys exist; thecopies.first()selection rule and the multi-copy note are unproven, and a variant taking its search roots as a parameter would make both testable.crates/rocm-core/src/examine.rs:1861— deletingmatches.sort()fails nothing, so the "sorted so the result does not depend on directory iteration order" promise has no test; the ordering test uses two separate directories and never exercises intra-directory order.crates/rocm-core/src/examine.rs:457— the comment justifying the call site says the search consultsLD_LIBRARY_PATH"(read byprobe_env)", butprobe_comgrre-reads the variable itself and uses nothingprobe_envstored; only theprobe_rocm_installhalf of the stated ordering dependency is real.crates/rocm-core/src/examine.rs:1907— the "N copies … would load" note fires on any host holding a system ROCm install alongside a runtime this CLI installed, which the commit message itself calls the normal case; harmless whileExamination.notesis JSON-only (no consumer prints it today), but it will read as a warning the moment one does.
juhovainio
left a comment
There was a problem hiding this comment.
Nice write-up, and the reasoning behind the non-obvious calls (no dlopen, resolved-path dedup, WSL2 inclusion) all held up.
One thing I think is a real gap: the managed-runtime search path doesn't look like it actually covers the wheel-install case this PR is meant to catch. managed_sdk_ld_library_path (the existing helper) finds a wheel's comgr copy by walking site_packages for _rocm_sdk_* siblings, because the actual libraries live in a sibling package, not under the root it's handed. comgr_search_dirs's new managed_runtime_roots() path reuses collect_sdk_library_paths on the root directly but skips that site_packages walk entirely - so for a wheel-format install this probe likely never finds the copy that's the PR's own headline scenario. Left an inline comment at the spot.
Two smaller notes, not blocking:
- The
/opt/rocm*sibling-install scan isn't mentioned in the PR description, which frames this as strictly system-vs-wheel. Worth a line in the write-up. collect_libraries_in_dirnow sorts before taking the first match, which does change the HIP probe's tie-break order when a directory has multiple matching files - minor, but the Risk section's "unchanged" claim isn't quite true for that edge case.
Also a couple of judgement-call code-smell notes worth a look when convenient (not blockers): the same 4-line "Unix-only" test comment is duplicated across three tests in examine.rs, and ComgrCopy.source is a closed 4-value set that reads like it wants to be an enum rather than a bare String.
| // managed runtime keeps its libraries is a second thing to keep correct. | ||
| for root in managed_runtime_roots() { | ||
| let mut paths = Vec::new(); | ||
| crate::collect_sdk_library_paths(&root, &mut paths); |
There was a problem hiding this comment.
This only walks root via collect_sdk_library_paths. The existing managed_sdk_ld_library_path (lib.rs) additionally walks candidate.site_packages for _rocm_sdk_* sibling packages, because that's where the actual libraries live for a wheel install - this loop doesn't have (and doesn't look up) an equivalent site_packages value for managed_runtime_roots()'s roots, so it likely misses a wheel-installed comgr copy entirely.
| // Sibling ROCm installs the active one does not cover. A versioned install | ||
| // left behind by an upgrade is one of the two copies this entry exists to | ||
| // find. | ||
| if let Ok(entries) = std::fs::read_dir("/opt") { |
There was a problem hiding this comment.
This /opt/rocm* sibling-install scan isn't mentioned in the PR description (which frames the conflict as system-install vs. wheel-install only) - worth a line explaining it's in scope too.
| .filter(|entry| entry.file_name().to_string_lossy().starts_with(prefix)) | ||
| .map(|entry| entry.path().to_string_lossy().into_owned()) | ||
| .collect(); | ||
| matches.sort(); |
There was a problem hiding this comment.
This sort changes the HIP probe's tie-break order when a directory has more than one matching file (previously OS-arbitrary read_dir order, now sorted). The Risk section says "the HIP probe still takes the first hit and its field is unchanged" - true for the single-match case, not quite for this edge case.
d9b0a75 to
f2bd0c9
Compare
6ea3a21 to
ee48870
Compare
|
🔴 Automated review · pr-review-watcher · ee48870 This automation never files a GitHub approval, so no approving review will appear here whatever the outcome — the merge decision stays with a human reviewer. SummaryThe change generalises the code object manager scan from find-first to find-all, adding four On the red check: I did find a defect in this diff that would deterministically fail any run that actually executes the new GPU-gated scenario (blocker 1). That scenario resolves to skip rather than fail where no AMD GPU is present, so whether it is the failing check depends on the lane; I cannot confirm which check failed and am not inferring one. Either way the defect is real and independent of the base branch's intermittency. 🚫 Blocking (must fix before merge)
Non-blocking
|
siloteemu
left a comment
There was a problem hiding this comment.
🔴 Automated review · pr-review-watcher · ee48870
Change request filed by automation. The findings below are the blocking half of the round published in the report comment on this pull request; the non-blocking notes stay there. This will be withdrawn once they are addressed — no human needs to clear it.
🚫 Blocking (must fix before merge)
-
tests/e2e-cucumber/tests/e2e/examine_steps.rs:859-872— the new step for scenario examine-17 asserts that every copy whosesourceismanaged-runtimecarries a non-emptyinstall_root.ComgrCopy(crates/rocm-core/src/examine.rs:159-177) has exactly four fields —path,real_path,version,source— and nothing anywhere in the workspace emitsinstall_rooton a comgr copy.copy.get("install_root")therefore always yieldsNone,unwrap_or_default()gives"", and the assertion fails on every execution. This is a test that cannot pass for the reason its message states, and it is the headline coverage the PR text offers for the managed case ("examine-17 asserts that on a GPU lane"), so the case is in fact uncovered and the scenario is a guaranteed red wherever a GPU is present. Fix: delete thefor copy in managed { … }loop and its preceding comment (lines 859-872). What remains — copies non-empty, and at least one withsource == "managed-runtime"— is still a non-vacuous assertion of the thing the scenario names. If the owning-install data is wanted now rather than in the follow-up, the alternative is to add the field toComgrCopyand populate it inrecord_comgr_copy, but that is the deferred work and should not be bolted on here. -
crates/rocm-core/src/lib.rs:4172-4177— the doc comment oncollect_sdk_package_library_pathsstates "Attribution is by longest known root and no_rocm_sdk_*directory is a known root, so each resolves to its runtime — which is what keeps a healthy managed install from looking like several installations in conflict." No attribution-by-longest-root mechanism exists in this diff or in the base; nothing computes an owning installation for a comgr copy, andcomgr_matches_runtimeis documented as permanently unset. The comment presents a safety property as provided when the code provides nothing of the kind, which is precisely the guard-in-prose-only pattern — and it is the same descoped feature that produced blocker 1. The commit body carries the same claim twice ("recording every copy with the installation that owns it, plus the same for the HIP runtime so the two can be compared" and "Ownership is matched against known installation roots, longest first, and never derived by walking up from the file"); a maintainer reading either will believe attribution shipped. Fix: cut the second and third sentences of the doc comment, keeping "They belong to the runtime that contains them, not to themselves.", and drop the two ownership paragraphs from the commit body when the branch is next amended.
ee48870 to
a8749a3
Compare
Superseded: this objection was filed against an earlier commit and is replaced by a fresh round at the current head. The earlier blocking item is discharged in the tree; a new, unrelated blocking finding is filed separately.
|
🔴 Automated review · pr-review-watcher · a8749a3 This automation never files a GitHub approval, so no approving review will appear here whatever the outcome — the merge decision stays with a human reviewer. SummaryThe change replaces the first-match-only 🚫 Blocking (must fix before merge)
Non-blocking
|
siloteemu
left a comment
There was a problem hiding this comment.
🔴 Automated review · pr-review-watcher · a8749a3
Change request filed by the automated review round at this commit. The full round, including non-blocking notes, is in the comment posted alongside it.
🚫 Blocking (must fix before merge)
tests/e2e-cucumber/features/examine.feature:184-194— examine-16 is untagged, so it runs on every lane, and its comment claims "What every lane can prove is that the inspection answers the question at all rather than staying silent, and that finding none is reported as a finding rather than a failure — which is the case the mock lane actually has." The code does not provide that; the struct defaults do. All four new fields are#[serde(default)]andExamination::default()already yieldscomgr_paths: []andcomgr_selected: null, which is exactly what both steps assert when no copy is found. Measured, not read: with bothprobe_comgr(&mut e)calls deleted, the assertion sequence ofassert_comgr_copies_reportedandassert_comgr_selection_is_stated(tests/e2e-cucumber/tests/e2e/examine_steps.rs:773,:800) still passes against the serialized examination. That is the state of every Windows lane, where the probe is never called at all, and of the mock lane the comment names. So on precisely the lanes where examine-16 is the only coverage of this change, it passes with the change reverted, while its prose says it proves the opposite — and examine-17 cannot compensate because it is@requires-gpu. Fix: tag the scenario@requires-os:linux(the probe is Linux and WSL2 only, so the Windows pass is vacuous by construction), and in the empty branch ofassert_comgr_selection_is_statedassert thatnotescontains the "nolibamd_comgrfound ..." entry thatcrates/rocm-core/src/examine.rs:1927-1930pushes, in addition tocomgr_selectedbeing null. That note is produced only byprobe_comgrrunning and finding nothing, so it distinguishes "probed, found none" from "never probed" — which is the distinction the scenario's own sentence claims to make, and the same distinction the PR already builds intocomgr_matches_runtime. Assert on a substring of the note rather than the whole string so the wording stays free to change; do not assert it in the non-empty branch, where a single copy pushes no note at all.
…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, so ownership is decided by matching against known installation roots, longest first -- 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. Fix CI: the managed-runtime comgr/HIP search resolved the data directory through `crate::runtime::default_data_dir` (`$HOME/.rocm` or the OS default) instead of `AppPaths::discover`, so it disagreed with the rest of the CLI, and found nothing at all, on any host where `ROCM_CLI_DATA_DIR` relocates the data directory -- which is exactly what every GPU e2e scenario does for isolation. That is why the search saw no managed runtime whatsoever and the GPU e2e lane (`examine-finds-the-managed-runtimes-own-compilation-library`) failed identically on every GPU family. Separately, once a runtime is found, its root resolves to its `_rocm_sdk_devel` package directory, and the search then guessed a venv layout (`root/lib/<python>/site-packages`) to find its sibling packages; that guess does not hold for a real install either, so the search now reads back the library directories the SDK probe already recorded (`library_paths`) instead of re-deriving them. Also fixes a needless-`collect` clippy lint in the e2e step, and tags diagnose-21 `@requires-os:linux`: fix-18-comgr-conflict is registered for linux/wsl only, so applying it for real on a native Windows lane hit the fix's own platform gate before ever reaching the advisory behavior under test. That data-dir fix cleared the scenario everywhere except the Strix Halo Windows lane, where it still failed CI after the rest of this commit landed: `comgr_paths`/`hip_paths` came back empty even with the managed runtime found and the right data dir in hand, because the whole search -- `LD_LIBRARY_PATH`, the loader cache, and the `lib/` directory scan -- is built around ELF shared-object names (`libamd_comgr`, `libamdhip64`). Native Windows ships the equivalent libraries under different names and has neither `LD_LIBRARY_PATH` nor a loader cache, so none of it applies there; WSL is unaffected, since it reports `os_family` "linux" and the runtime underneath really does carry a `.so`. This is the same boundary `fix-18-comgr-conflict` is already gated on (`&["linux", "wsl"]`, addressed above for diagnose-21), just missed for this scenario when it was introduced in #384 -- so examine-17 now carries the same `@requires-os:linux` tag, pre-existing gap, not a regression from this commit. Signed-off-by: Eugene Volen <Eugene.Volen@amd.com>
a8749a3 to
51a20ff
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, so ownership is decided by matching against known installation roots, longest first -- 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. Signed-off-by: Eugene Volen <Eugene.Volen@amd.com>
Addressed: examine-17 (renumbered) is now tagged @requires-os:linux and its empty-result assertion requires the "no libamd_comgr found" note, so it can no longer pass with probe_comgr reverted or never called. Verified by mutation-testing the note (suppressed it, watched the scenario fail, restored it).
51a20ff to
c5c15f7
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, so ownership is decided by matching against known installation roots, longest first -- 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. Signed-off-by: Eugene Volen <Eugene.Volen@amd.com>
|
🔴 Automated review · pr-review-watcher · e33f8ee This automation never files a GitHub approval, so no approving review will SummaryThe PR changes 🚫 Blocking (must fix before merge)
Earlier change request (review 5392128801, filed at
|
rominf
left a comment
There was a problem hiding this comment.
🔴 Automated review · pr-review-watcher · c5c15f7
Change request filed by the automated review round at this commit. The full round, including the detail for each item and the non-blocking notes, is in the comment posted alongside it. It will be withdrawn by the automation once these are addressed.
🚫 Blocking (must fix before merge)
crates/rocm-core/src/examine.rs:2214-2244:comgr_selectedand the "N copies … X would load" note name theld.so.cachesystem copy when a managed wheel runtime is present and the shell'sLD_LIBRARY_PATHis empty. The processes the CLI launches against that runtime prepend itslibrary_entries(engines/lemonade/src/process.rs~L220-229), so the wheel copy is what actually loads. Directories that are not on any loader path (/opt/rocm*/lib, managed-runtime) can also become "would load". Restrict the selection to real loader-path copies, or compute it under the managed runtime's environment, and pin the chosen behaviour with a unit test.crates/rocm-core/src/lib.rs:4159-4168: thesite_packages: Nonefallback scansroot/lib{,64}/*/site-packages. The installer recordsroot_pathas<site-packages>/_rocm_sdk_devel(apps/rocm/src/therock.rs:5279), so that path never exists. The test fixture (examine.rs:3383) uses a venv-root layout the installer never produces. Either fix the fallback (useroot.parent()for_rocm_sdk_*roots) and build the fixture from the real shape, or drop the fallback and its test.- The commit message, which is the PR's only description, claims things the diff does not do: a HIP copy list, a search that works with no registry record, and a single shared description of the loader path that launched processes use. Make the message match the code.
probe_comgr,comgr_paths_in_loader_cacheandcomgr_search_dirs(examine.rs:2214-2371) have no tests. Source ordering,ldconfig -pparsing, the/opt/rocm*scan, the selection and the multi-copy note can all break with every test still green. Split out a pure function and unit-test it.
c5c15f7 to
a6d7af6
Compare
juhovainio
left a comment
There was a problem hiding this comment.
I reviewed this PR and found one substantive issue plus some description and structure notes, left inline below.
The substantive one: the site_packages: None fallback is unreachable, and its directory shape could not match even if it were reached. It ships a seven-line comment explaining that it rescues a host whose registry is missing or stale, which it cannot do, since a missing registry produces zero candidates upstream. The test that appears to cover it only passes because its fixture inverts the real parent/child relation, and the description offers those tests as the substitute for e2e coverage.
The rest: collect_libraries_in_dir now sorts, which can change which library the HIP probe picks, and the description says that field is unchanged. The visibility change is four symbols and two struct fields rather than one helper. And examine-19 looks fragile on a healthy host.
Things I checked and did not flag: the #[serde(default)] justification holds, mid-struct field insertion is fine here, the probe_env HIP rewrite is equivalent, and the @requires-os:linux tagging is right.
| paths: &mut Vec<PathBuf>, | ||
| ) { | ||
| collect_sdk_library_paths(root, paths); | ||
| match site_packages { |
There was a problem hiding this comment.
The None arm of this match is unreachable, and its directory shape could not match if it were reached.
Unreachable: ROCM_SDK_PROBE_SCRIPT sets site_packages unconditionally, outside the try, at apps/rocm/src/therock.rs:5228, and has done since the initial import 837067fd. No candidate reaches here with None.
Could not match anyway: root_path is already inside site-packages, pinned by the probe's own contract test at apps/rocm/src/therock.rs:9696-9698. So the fallback's root/lib/<python>/site-packages is a directory no managed runtime has.
The seven-line comment above it says it rescues "a host whose registry is missing or stale". It cannot: a missing registry yields zero candidates at lib.rs:4060, so there is nothing for this arm to run on.
Deleting the arm and taking site_packages: &Path would make the signature say what is actually true.
| /// nothing wrong rather than a machine we failed to inspect. | ||
| #[cfg(target_os = "linux")] | ||
| #[test] | ||
| fn a_wheel_runtime_with_no_registry_record_is_still_found() { |
There was a problem hiding this comment.
This test only looks like it exercises the fallback above. Its fixture puts site_packages under root_path, which is the reverse of the real relation, where root_path lives inside site-packages (pinned at apps/rocm/src/therock.rs:9696-9698).
That matters more than usual here because the description offers these unit tests as the substitute for e2e coverage of this path. Right now they certify a shape production never produces.
| /// An unreadable directory contributes nothing and is not an error: the library | ||
| /// path routinely names directories that do not exist, and a probe that failed | ||
| /// on one would report nothing about the machine it was asked to describe. | ||
| fn collect_libraries_in_dir(dir: &std::path::Path, prefix: &str, found: &mut Vec<String>) { |
There was a problem hiding this comment.
collect_libraries_in_dir now sorts its results, so the HIP probe taking "the first hit" can select a different library than before on a directory holding more than one match. The description says "The HIP probe still takes the first hit and its field is unchanged", which understates this: the mechanism is unchanged, the outcome need not be.
Worth either stating the ordering change in the description or confirming explicitly that no multi-match directory exists in practice.
| /// not become a second change to the wire contract, and so a consumer can | ||
| /// tell "not yet answered" from "answered no". | ||
| #[serde(default)] | ||
| pub comgr_matches_runtime: Option<bool>, |
There was a problem hiding this comment.
comgr_matches_runtime is always None as of this PR, with the consumer arriving in #385. That is a reasonable way to split a stack, it just means this field ships inert, and anything reading rocm examine --json in between sees a null that means "not computed" rather than "no mismatch". Worth a line in the description.
Two structural notes while here. The description says "One existing helper was made pub(crate)", but it is four symbols plus two struct fields. And flattening TheRockSdkProbeCandidate into anonymous tuples is what forces those exports: the fields travel together everywhere, so keeping the struct and passing it whole would cost fewer exports and read better at the call sites.
Last one, minor: roughly 60 new lines go into lib.rs, which is already past 13,000 lines. AGENTS.md section 6 asks for new subsystems to get their own file, and while this is an extension rather than a new subsystem, the managed-runtime path-collection helpers now form a coherent group that would sit well on its own.
| # still runs on the WSL lane, where the managed runtime really does carry a | ||
| # `.so`. | ||
| @id:examine-finds-the-managed-runtimes-own-compilation-library @requires-gpu @requires-os:linux | ||
| Scenario: examine-19 - The inspection finds the compilation library the CLI installed itself |
There was a problem hiding this comment.
This scenario looks fragile on a healthy host. Dedupe keeps the first label, so if the managed runtime's library directory is on LD_LIBRARY_PATH, which is a normal state after serving, the copy is labelled ld-library-path rather than as the managed runtime and the scenario fails.
Separately, the description's e2e count of 15 does not reconcile with the 19 scenarios in the file.
a6d7af6 to
e33f8ee
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 #384's review added to the comgr-only search: the active managed 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. Signed-off-by: Eugene Volen <Eugene.Volen@amd.com>
rominf
left a comment
There was a problem hiding this comment.
🔴 Automated review · pr-review-watcher · e33f8ee
Change request filed by the automated review round at this commit. The detail for each item, and the non-blocking notes, are in the round's report comment. The automation will withdraw this request once these items are addressed. The earlier change request (filed at c5c15f73) also stays standing: part of its item 1 still holds, as the report explains.
🚫 Blocking (must fix before merge)
crates/rocm-core/src/examine.rs:3584,:3620,:3650:the_active_runtimes_own_copy_outranks_the_ambient_environment,ld_library_path_outranks_loader_cache_and_search_dirsandthe_multi_copy_note_only_fires_past_one_copyhave no#[cfg(unix)]gate.- They feed a Windows temp path into a
':'-splitLD_LIBRARY_PATHparser, so they fail on the requiredwindows-build-and-testlane. They are the three failures on this head. - Fix: gate them
#[cfg(unix)], like the neighbouring LD-path tests.
- They feed a Windows temp path into a
- examine-19 (
tests/e2e-cucumber/tests/e2e/examine_steps.rs:1103-1126) cannot pass on its GPU lane.- With a managed runtime active, its
_rocm_sdk_core/libis already among the active interpreter'slibrary_paths. Sofind_comgr_copieslabels the copyactive-runtime, and dedupes away the latermanaged-runtimehit the step asserts on. - Fix: assert that the CLI-installed copy is found, whichever of those two labels it carries, or pin the intended label semantics with a unit test over overlapping dirs.
- With a managed runtime active, its
… first
HIP compiles device code at run time through libamd_comgr, and a machine
can hold more than one copy. The loader picks one. When the copy it picks
does not belong to the active HIP runtime, compilation fails with an
error naming neither the library nor the second copy.
The search stopped at the first match, so the second copy could not be
seen at all. It now records every copy found, in loader search order.
Attributing each copy to the installation that owns it, and doing the
same search for the HIP runtime so the two can be compared, is deferred;
comgr_matches_runtime stays permanently unset until that lands.
The managed search now reads the runtime registry rather than listing
<data>/runtimes on disk. A directory listing yields a root and nothing
else, and the root alone does not locate a wheel runtime's libraries --
those live in a sibling _rocm_sdk_* package inside the interpreter's
site-packages, which is why `managed_sdk_ld_library_path` (the
environment the CLI builds to run an SDK tool, such as
rocm_agent_enumerator, inside a managed runtime) walks that directory.
Searching the root alone therefore missed the copy this CLI installs
itself, which is the case the entry exists for.
That knowledge now lives in one place for this search:
collect_managed_runtime_library_paths is shared by the comgr search here
and by managed_sdk_ld_library_path, so that one layout is described once
there rather than twice. A third description, collect_runtime_environment_paths,
still builds the environment a served engine process gets; unifying all
three is follow-up work, not part of this search.
Listing the directory was also a second answer to "which runtimes exist",
where the registry is the first one, and only the registry records which
site-packages an interpreter uses.
The record is preferred but not required. A read-only probe cannot depend
on one being present and current, and a host whose registry is missing or
stale is exactly the kind this command is called on: reporting no copies
there would read as a machine with nothing wrong rather than one we
failed to inspect. With no record, root's own `_rocm_sdk_*` name is read
to find its parent -- the site-packages directory its siblings share --
and a miss costs a directory that is simply not reported.
No dlopen. Reading the version through amd_comgr_get_version would run an
unknown library's initialisers on a machine called on precisely because
something is already wrong, and load a possibly conflicting HIP stack
permanently into the process. The version is read from the versioned
soname instead, and a name carrying none yields an empty version rather
than a confident wrong one.
Directory matches are now sorted, which makes the HIP probe's tie-break
deterministic where one directory holds several matching files. It used
to take whatever read_dir yielded first. Better, but a change, not a
no-op.
The loader-cache tier used to gate on which("ldconfig"), which only walks
$PATH -- and ldconfig lives in /sbin, off a non-root user's PATH on Debian
and derivatives. That silently dropped the one tier that finds a copy
registered only in ld.so.cache; it now reuses the fallback search
ldconfig_cache() already does for the same reason.
Covered at two levels, because neither is sufficient alone. Unit tests
build a wheel-format layout directly, which proves the search understands
a layout we described; only a real managed runtime proves it matches the
one the installer produces, so examine-19 asserts that on a GPU lane.
The four new fields are `serde(default)`. This structure is read back
from another machine: `rocm remote doctor` deserializes an examination
the remote's own CLI produced, and that CLI may predate these fields.
Without a default, adding one here refuses every remote running an older
build, reported as "the remote CLI is probably a different version" --
true, and useless, since the older CLI is the one that cannot be changed.
Fix CI: managed_runtime_roots resolved the data directory through
crate::runtime::default_data_dir (`$HOME/.rocm` or the OS default)
instead of AppPaths::discover, so it disagreed with the rest of the CLI
and found nothing at all on any host where ROCM_CLI_DATA_DIR relocates
the data directory -- which is exactly what every GPU e2e lane sets for
isolation. That is why the new GPU scenario
(examine-finds-the-managed-runtimes-own-compilation-library) failed
identically on every GPU family; it now resolves through AppPaths::discover
like everything else in the CLI. Also fixes a needless-collect clippy
lint in the e2e step, and tags that scenario @requires-os:linux: the
search looks only for libamd_comgr by its ELF soname, which native
Windows does not ship.
Rebasing onto main picked up two unrelated scenarios that claimed the same
numbers in turn -- examine-16 (EAI-8950, #444), then examine-17 (EAI-8449,
#434) -- so this commit's two new scenarios are renumbered to examine-18
and examine-19 to keep every scenario number in the file unique.
Review: examine-18 (the host-independent code-object-manager scenario) was
untagged and its empty-result assertion matched `Examination::default()`
alone, so it would pass with `probe_comgr` deleted outright -- on native
Windows, where the probe never runs, that is not a hypothetical. Tagged
@requires-os:linux and the assertion now also requires the "no libamd_comgr
found" note that only a probe which actually ran and came up empty pushes.
Verified the new assertion fails by temporarily suppressing that note and
watching the scenario go red, then restored it.
Review: four more issues. (1) comgr_selected and the multi-copy note named
the ld.so.cache system copy as "would load" whenever a managed wheel
runtime was active but this process's own LD_LIBRARY_PATH was empty --
wrong, because rocm serve/rocm chat prepend the active runtime's own
library directories onto LD_LIBRARY_PATH before launching the engine, so
the wheel copy is what a served process actually loads. probe_comgr now
takes the active runtime's interpreter (the same FrameworkInterpreter
already threaded through probe_framework) and searches its library_paths
first, ahead of the ambient environment; pinned by
the_active_runtimes_own_copy_outranks_the_ambient_environment. (2) The
site_packages: None fallback in collect_managed_runtime_library_paths
guessed a venv layout (root/lib/<python>/site-packages) that the installer
never produces -- root is one of the runtime's own _rocm_sdk_* package
directories, not a venv root, so the guess always found nothing. It now
reads root.parent() instead, which is the site-packages directory the
guessed layout could never reach. The test fixture
(wheel_runtime_on_disk) was built from the same wrong venv shape and so
could not have caught this; rebuilt it from the real installer layout
(ROCM_SDK_PROBE_SCRIPT's root_path), and confirmed
a_wheel_runtime_with_no_registry_record_is_still_found fails against the
old fallback before passing against the fix. (3) probe_comgr,
comgr_paths_in_loader_cache, and comgr_search_dirs had no tests at all, so
the source ordering, the ldconfig -p parsing, the /opt/rocm* scan, the
selection, and the multi-copy note could all break with the rest of the
suite still green. Split the ordering/selection logic into a pure
find_comgr_copies, the ldconfig -p line parsing into
parse_ldconfig_cache_paths, and the /opt scan into rocm_install_siblings
(opt directory taken as a parameter rather than hardcoded, so a test can
point it at a temp folder instead of the real /opt on the machine running
the suite); each is unit-tested directly, and each new test was confirmed
to fail against a deliberately broken version of the function it covers
before being restored. (4) This message previously claimed a HIP copy
list and a single shared description of the loader path launched
processes use; neither matches this diff. Corrected above.
Review: a second round, one automated and one human, reaching the same
place by different routes.
The human reviewer confirmed, independently, that the site_packages:
None fallback added in the prior round could not actually fire:
ROCM_SDK_PROBE_SCRIPT has recorded site_packages unconditionally, outside
its try, since the probe's initial version, so every manifest that
parses at all carries Some there. The test built to cover it could not
have caught that either way, since it called the function directly with
None rather than through a real candidate. Rather than keep a reachable-
looking branch and a comment describing behaviour the code does not
have, the None arm is now a no-op (root's own directories, already
collected unconditionally above, are what a hand-constructed None caller
still gets) and the test is removed. The neighbouring root-format test
is kept and re-targeted: it already called the same function directly
with None, so it is what actually exercises that arm's contract, not the
removed test.
The automated reviewer caught a real regression the first round
introduced: three new unit tests feed a path into the `:`-split
LD_LIBRARY_PATH parser without a unix cfg gate, same as the pattern the
file already uses for that reason, and that broke the required
windows-build-and-test lane. Gated, along with the helper they share
(which would otherwise be unused, and clippy-denied, once its only
callers are gated).
It also found that the active-runtime-priority fix regresses
examine-finds-the-managed-runtimes-own-compilation-library on a healthy
host: once a managed runtime is active, its directory is found first and
labelled active-runtime, and the dedup this search has always done on
resolved path then drops the later managed-runtime hit for the identical
file -- the scenario's step asserted on the label that fix makes
unreachable. The step now accepts either label as proof the CLI's own
installed copy was found, and a new unit test
(an_active_runtime_directory_that_is_also_a_managed_root_keeps_one_label)
pins that an overlapping directory keeps exactly one label rather than
being reported twice; confirmed to fail when the active-runtime search is
moved after the others, then restored.
Signed-off-by: Eugene Volen <Eugene.Volen@amd.com>
e33f8ee to
ba1d7fb
Compare
What
HIP compiles device code at run time through
libamd_comgr, and a machine can hold more than one copy — a system ROCm install and a ROCm Python wheel each ship one. When the copy the loader picks does not belong to the active runtime, compilation fails with a general error naming neither the library nor the second copy.This CLI installs the second copy itself. Its install path puts ROCm wheels into a managed environment, so a user who follows it on a host that already has system ROCm ends up holding both, having done nothing unusual and been warned about nothing.
Nothing looked past the first match, so the second copy was invisible. The inspection now records every copy in loader search order, which one would load, and its version.
Reporting only. The conflict rule and the catalog entry that states the user's options follow in a second PR. That is where the risk of a false report on a healthy managed install sits — every managed environment this CLI creates legitimately holds two copies — so it belongs with the rule it threatens rather than bundled with an uncontroversial probe.
Non-obvious decisions
No
dlopen. The ticket specified reading the version viaamd_comgr_get_versionthroughdlopen. That runs the library's ELF initialisers — code from an unknown library, on a machine the tool was called to precisely because something is already wrong — and pulls a possibly conflicting HIP stack permanently into the process. The version is read from the versioned soname instead. A renamed file yields an empty version, which is honest; the conflict rule never needs the version, only the report text does.Deduplicated on the resolved path. ROCm ships
libamd_comgr.so.2.8.0beside an unversioned symlink. Counting those as two copies would invent a conflict on an ordinary install — the false report that matters most, since it would fire on healthy machines.It emulates the loader; it is not the loader. No account is taken of
RUNPATH/RPATH,ld.so.preload, or a container remapping paths. The doc comment says so, and the list of copies is the evidence for the verdict rather than a guarantee.It runs on WSL2 too — a deliberate departure from the ticket's "Linux" scope. The WSL2 early return exists for kernel driver questions, and the code says so while still running the framework probe. A shadowed comgr copy is a run-time compilation failure inside the framework, and ROCm on WSL2 is a supported configuration where the two copies collide identically. Skipping it would leave a WSL user unable to see a conflict that is really there.
Four existing symbols, plus two struct fields, were made
pub(crate)rather than restating where a managed runtime keeps its libraries:ldconfig_cache,managed_therock_sdk_probe_candidates,collect_managed_runtime_library_paths,collect_sdk_library_paths, andTheRockSdkProbeCandidate'ssite_packages/root_pathfields. A second description of that layout is a second thing to keep correct.collect_libraries_in_dirnow sorts its results. That is a behavior change, not purely additive: where a directory holds more than one file matchinglibamd_comgr/libamdhip64, the HIP probe's "first hit" can now be a different file than the OS-arbitraryread_dirorder it used before. Deterministic is the better answer for a report two people compare side by side, but it is worth stating plainly rather than filing this PR as behavior-preserving outside the one field it says it touches.Corrections to the ticket
examinealready reports the active runtime's owning installation. It does not —Examinationrecords the system install only. That knowledge lives in a separaterocm examinesurface. Part 2 needs it, androcm-core's own runtime helpers can resolve managed runtime roots, so no cross-crate dependency is required.Examinationhas no text renderer — it surfaces through--jsonand feedsdiagnose. Putting them in the separate text report would mean a second, independent probe. Deferred to part 2, where the diagnosis puts the conflict in front of the user in prose.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 failureexamine-finds-the-managed-runtimes-own-compilation-library,@requires-gpu @requires-os:linux) run in CI, not locallyEvery new or changed unit test was verified to fail, not just to pass, each against a deliberately broken version of the thing it covers, then restored. See "Review remediation" below for the round added after the first review.
Coverage boundary
The e2e suite cannot install a second ROCm stack, so it cannot prove the two-copy case. It proves the surface every lane has: the inspection answers the question, the reported copies carry enough to act on, and the selected copy agrees with the list. The two-copy, symlink-dedupe, version-parsing, active-runtime-priority, and attribution rules are proven by unit tests that build the directory layout directly. This is a real boundary, not an oversight.
Risk
Medium, not Low as originally assessed: a first review round found the
LD_LIBRARY_PATH/loader-cache search could name the wrong copy as "would load" on a managed install, and a fallback path that could never actually resolve on a real one. Both are fixed now, each with a regression test confirmed to fail against the old behavior first (see "Review remediation"). Still additive in shape — four new fields and a probe that only reads — but no longer risk-free by construction the way the first version was.The
Examinationfield set is a frozen wire contract with a test guarding it; that test fired on this change and was updated deliberately, which is the guard working as intended.Review remediation
A first review round found four issues, all addressed in this commit:
comgr_selectedand the multi-copy note named theld.so.cachesystem copy as "would load" whenever a managed wheel runtime was active but this process's ownLD_LIBRARY_PATHwas empty — wrong, becauserocm serve/rocm chatprepend the active runtime's own library directories ontoLD_LIBRARY_PATHbefore launching the engine, so the wheel copy is what a served process actually loads. The search now takes the active runtime's interpreter (the same one already threaded through the framework probe) and searches its library directories first, ahead of the ambient environment.site_packages: Nonefallback incollect_managed_runtime_library_pathsguessed a venv layout (root/lib/<python>/site-packages) the installer never produces —rootis one of the runtime's own_rocm_sdk_*package directories, not a venv root, so the guess always found nothing. It now readsroot.parent()instead, which is the site-packages directory the guess could never reach. The test fixture was built from the same wrong venv shape and so could not have caught this; it is rebuilt from the real installer layout, and confirmed to fail against the old fallback before passing against the fix.probe_comgr's internals had no unit tests at all, so the source ordering, theldconfig -pparsing, the/opt/rocm*scan, the selection, and the multi-copy note could all break with the rest of the suite still green. The ordering/selection logic is split into a purefind_comgr_copies, theldconfig -pline parsing intoparse_ldconfig_cache_paths, and the/optscan intorocm_install_siblings(the directory to scan is now a parameter, not hardcoded, so a test can point it at a temp folder); each is unit-tested directly.A second round, one automated and one human, reaching the same place by different routes:
ROCM_SDK_PROBE_SCRIPThas recordedsite_packagesunconditionally since its initial version, so every manifest that parses at all carriesSome, and the test for it called the function directly withNonerather than through a real candidate either way. Rather than keep a reachable-looking branch and a comment describing behavior the code does not have, theNonearm is now a no-op and the test is removed; the neighboring root-format test, which already called the function directly withNone, is what actually exercises that arm's contract and is kept.#[cfg(unix)]gate and fed a path into the:-splitLD_LIBRARY_PATHparser, failing the requiredwindows-build-and-testlane — the entire Windows failure on this branch. Gated, along with the helper they share.examine-finds-the-managed-runtimes-own-compilation-libraryon a healthy host: once a managed runtime is active, its directory is found first and labelledactive-runtime, and this search's existing dedup-by-resolved-path then drops the latermanaged-runtimehit for the identical file — the scenario's step asserted on the label that fix makes unreachable. The step now accepts either label, and a new unit test pins that an overlapping directory keeps exactly one label, confirmed to fail when the active-runtime search is reordered to run last.Full detail is in the commit message's two
Review:paragraphs.