Repository navigation
refactor: find a Python environment root from its interpreter by one rule - #922
Closed
jussielo-amd wants to merge 6 commits into
Closed
jussielo-amd wants to merge 6 commits into
jussielo-amd wants to merge 6 commits into
Conversation
…terpreter Three private helpers turn an interpreter path into the environment folder that contains it, and they do not agree. ComfyUI's runtime environment returns a root only when the interpreter's folder is bin or Scripts (any case) and otherwise leaves VIRTUAL_ENV unset. The adopt command and the vLLM launch both take the grandparent whatever the folder is called: the bin/Scripts check in the adopt helper returns the same value on both of its branches, and vLLM never had one. So an interpreter in an unrelated folder is guessed at by two callers and refused by the third. Add tests that pass on the unmodified code and pin each caller's behaviour for bin, Scripts, SCRIPTS, an unrelated folder, a bare file name and a root-level interpreter, including those differences, so the change that makes the callers agree shows exactly what moves. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
…rule The adopt command, the ComfyUI runtime environment and the vLLM launch each kept a private copy of "which environment folder holds this interpreter", and the copies disagreed. ComfyUI gave a root only for an interpreter in a bin or Scripts folder; adopt and vLLM took the grandparent whatever the folder was called (adopt's bin/Scripts check returned the same value on both branches). An interpreter in an unrelated folder was refused by one caller and guessed at by the other two. Add runtime_python_env_root_for_executable to rocm-core, beside the helpers it inverts, and use it from all three. An interpreter belongs to an environment only when it sits directly in a bin or Scripts folder, in any case; everything else is None, because a guessed folder is not an environment. The engine crate cannot reach apps/rocm, so rocm-core is the shared place. Valid venv layouts resolve exactly as before. Two things change for an interpreter outside that layout: `rocm runtimes adopt --python` without --root now stops and asks for --root instead of adopting the interpreter's grandparent, and the vLLM launch fails with its existing "not inside a venv" error instead of searching the wrong folder. Each caller's test goes through the shared function, scenario runtime-23 covers the adopt refusal end to end, and the --root help text, README and docs/testing.md now say how the folder is found. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
jussielo-amd
marked this pull request as ready for review
November 5, 2026 17:20
jussielo-amd
requested review from
tomastola
and
a balanced review from Copilot
November 5, 2026 17:20
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
No active review issues remain for the change centralizing Python environment-root inference.
No open issues 🎉
What changed in this PR
Centralizes Python environment-root inference in rocm-core so runtime adoption, ComfyUI, and vLLM use the same rule instead of separate implementations.
Changes:
- Adds a shared helper accepting interpreters directly inside
binorScripts, case-insensitively. - Stops adoption and vLLM from guessing roots for other layouts, while preserving ComfyUI behavior.
- Adds regression tests and an adoption-refusal scenario, and updates help and documentation.
| File | Description |
|---|---|
tests/e2e-cucumber/tests/e2e/runtime_steps.rs |
Adds interpreter fixtures and refusal/registration assertions. |
tests/e2e-cucumber/features/runtime_setup.feature |
Adds the missing-root adoption refusal scenario. |
README.md |
Explains inferred roots and when --root is required. |
engines/vllm/src/process.rs |
Uses shared inference and tests invalid layouts. |
docs/testing.md |
Documents adoption behavior and scenario coverage. |
docs/architecture.md |
Documents the shared helper and its callers. |
crates/rocm-core/src/runtime.rs |
Implements and tests the shared inference rule. |
crates/rocm-core/src/lib.rs |
Re-exports the helper. |
apps/rocm/src/runtime_adopt.rs |
Replaces local inference and tests root handling. |
apps/rocm/src/main.rs |
Clarifies the adopt --root help text. |
apps/rocm/src/comfyui/runtime_env.rs |
Uses shared inference and tests VIRTUAL_ENV behavior. |
Review effort: Balanced
…nv-root-rule Signed-off-by: Jussi Elo <jussi.elo@amd.com> # Conflicts: # docs/architecture.md
…nv-root-rule Signed-off-by: Jussi Elo <jussi.elo@amd.com>
…nv-root-rule Signed-off-by: Jussi Elo <jussi.elo@amd.com>
…nv-root-rule The only conflict was apps/rocm/src/main.rs: main moved the clap command tree into cli.rs, so the `rocm runtimes adopt` --root doc-comment edit from this branch is re-applied there and main.rs is taken as on main. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
Collaborator
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes ROCMAI-878. Three places turned a Python interpreter's path into "the environment folder this interpreter belongs to", and they gave different answers for the same path. The ComfyUI runtime environment returned a folder only when the interpreter sat in a
binorScriptsfolder (any case) and otherwise leftVIRTUAL_ENVunset.rocm runtimes adoptand the vLLM launch both took the interpreter's grandparent whatever its own folder was called: the adopt helper had abin/Scriptscheck, but both of its branches returned the same value, so it was dead code, and the vLLM copy never had a check at all. An interpreter in some unrelated folder was therefore refused by one caller and guessed at by the other two, and a guess names a folder that is not an environment.This replaces the three private copies with one rule in
rocm-core,runtime_python_env_root_for_executable, next to the existingruntime_python_env_bin_dirandruntime_python_executable_in_envthat it inverts. An interpreter belongs to an environment only when it sits directly in abinorScriptsfolder, in any letter case and on any host, and the answer is that folder's parent. Anything else returnsNone: another folder, a bare file name, a file at the filesystem root. The engine crate cannot depend onapps/rocmandrocmdmust never depend onrocm, sorocm-coreis the one place all three callers can reach (cargo xtask check-crate-edgespasses, no new edge).I chose the ComfyUI behaviour as the winner because refusing is the only answer that cannot be wrong. The grandparent of an interpreter in
/opt/tools/pythonis/opt, whichadoptwould have registered as a runtime and the vLLM launch would have searched forlib/python3.*/site-packages. Each caller already had a way to say no, and the two that were guessing now use it.What moved
infer_python_env_rootinapps/rocm/src/runtime_adopt.rs(grandparent, deadbin/Scriptsbranch)resolve_adopt_python_inputcallsrocm_core::runtime_python_env_root_for_executableinfer_python_env_rootinapps/rocm/src/comfyui/runtime_env.rs(bin/ScriptsorNone)venv_rootsites call the shared functionpython.parent().and_then(Path::parent)invllm_venv_root,engines/vllm/src/process.rsruntime_python_env_root_for_executableincrates/rocm-core/src/runtime.rs, re-exported at the crate rootUser-visible behaviour
Only the two callers that guessed change, and only for an interpreter that is not directly inside a
binorScriptsfolder. For a valid venv layout (<env>/bin/python,<env>/Scripts/python.exe, any letter case) every caller returns exactly what it returned before. The complete list of differences:rocm runtimes adopt --python <file>without--root, where the file's folder is neitherbinnorScripts. Before: the interpreter was probed and its grandparent folder was adopted as the runtime's install root. Now: the command stops with the existing messagecould not infer the Python environment folder from <path>; pass --rootand registers nothing. Passing--rootstill adopts that same interpreter, as before. The--roothelp text and the README'sadoptparagraph now say this (the help text used to call--root"Optional SDK root path").binorScriptsfolder. Before: the grandparent was used as the venv, so the launch either failed with a read error on alibfolder that does not belong to the environment or, worse, found apython3.*/site-packagesunder the wrong folder. Now: it fails up front with the error that was already written for this case,python executable ... is not inside a venv (bin/Scripts) layout. A runtime recorded byrocm install sdkor byadoptalways has its interpreter inbin/Scripts, so this is reachable only through a hand-edited or imported manifest.VIRTUAL_ENVis set forbin,Scripts,SCRIPTSandBIN, and left unset otherwise, before and after.A relative interpreter path such as
bin/pythonstill yields an empty folder from all three callers, as before; the adopt command makes its input absolute first and the manifests store absolute paths, so I left that alone.There is a scenario for item 1:
runtime-23(@id:runtime-adopt-outside-a-venv-asks-for-root, Linux, ordinary E2E lane). It plants a stand-in interpreter in atoolsfolder, adopts it without--root, and asserts the refusal asks for--rootand thatrocm runtimes liststill shows no runtime.runtime-07(adopting/usr/bin/python3with--root /opt/rocm) is unaffected and still passes. Item 2 is internal to a launch path that needs a real vLLM install, so it is covered by the unit test below rather than a scenario. The "--rootclears the refusal" half of item 1 is covered by the unit testadopt_request_without_an_inferred_root_asks_for_root, because adopting for real needs a genuine ROCm SDK environment.Verification
Ran:
cargo fmt --all --check;cargo clippy -p rocm -p rocm-core -p rocm-engine-vllm -p e2e-cucumber --all-targets -- -D warnings;cargo test --no-fail-fastforrocm-core,rocm-engine-vllmandrocm;cargo xtask manifest --check;cargo xtask check-crate-edges; theruntime-07andruntime-23scenarios throughcargo xtask e2e -- -n "runtime-(07|23)"; the leak scan againstorigin/main. No test was removed.The tests fail when the rule re-diverges. The shared rule has its own table in
rocm-core(bin,BIN,Scripts,SCRIPTS,scripts, an unrelated folder, a bare file name, a root-level file, and a round trip throughruntime_python_executable_in_env; paths are built withjoin, so they mean the same on Linux and Windows). Each caller has its own test that goes through the shared function:runtime_environment_exports_virtual_env_only_for_a_bin_or_scripts_folder(ComfyUI),adopt_python_file_infers_a_root_only_from_a_bin_or_scripts_folderandadopt_request_without_an_inferred_root_asks_for_root(adopt),vllm_venv_root_needs_a_bin_or_scripts_folder(vLLM). I checked each one goes red by breaking the code it covers; the evidence is in the hand-back notes rather than repeated here.Not run: the full workspace gate,
scripts/smoke_local.pyand the rest ofcargo xtask e2e(CI carries them). Thecfg(windows)side was not separately exercised beyond the shared function's host-independent table, which runs on the Windows job too.Notes for reviewers
Read the two commits in order. The first adds only tests that pass on unmodified
origin/mainand pin each caller's behaviour, including the divergences; the second changes the rule, flips the two assertions that move (adopt and vLLM for the unrelated-folder case), adds the shared function's own tests, the scenario, the docs and thedocs/architecture.mdentry.runtime-23is the next free scenario number at the time of writing and will need renumbering if another scenario lands first: #906 is still open and addsruntime-23toruntime-25, so whichever of the two merges second renumbers its scenarios.runtime_adopt.rs,comfyui/runtime_env.rsandengines/vllm/src/process.rsare also touched by refactors in flight, but in different regions.