Repository navigation
fix(install): preview an SDK install without preparing Python - #906
jussielo-amd wants to merge 10 commits into
Conversation
On a host with no usable Python, rocm install sdk --dry-run (and the install_sdk_dry_run assistant tool) went on to download a managed uv, have it install Python and save that Python's path in the config before printing a plan, although a preview promises to change nothing. The preview only needs the interpreter's wheel tags, so it now reports that a real install would prepare a managed Python (python_note) and prepares nothing; every refusal the real resolution raises is raised for the preview too. A real install resolves and prepares exactly as before. Adds unit tests for the no-Python, PATH Python, saved Python, stale saved Python and bootstrap-disabled cases, and scenario runtime-22, which checks the preview's note together with the empty tools, cache and config folders and a recording uv that is never run. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
There was a problem hiding this comment.
🟡 Changes recommended
The preview can report an incompatible managed-Python plan as valid, and the E2E cache assertion can fail because of pre-existing shared-cache contents.
2 open issues
What changed in this PR
This PR separates Python discovery from preparation so SDK dry-run previews can report a planned managed Python without downloading tools or saving its path. Real installs retain the existing preparation flow.
Changes:
- Add preview-aware Python planning and explanatory output.
- Add unit and end-to-end regression coverage, including recorded
uvinvocations. - Document preview behavior and its remaining package-index cache writes.
| File | Description |
|---|---|
tests/e2e-cucumber/tests/e2e/therock_steps.rs |
Add no-Python fixtures and side-effect assertions. |
tests/e2e-cucumber/src/bin/fake-uv.rs |
Add optional invocation logging. |
tests/e2e-cucumber/features/runtime_setup.feature |
Add a Linux preview regression scenario. |
docs/testing.md |
Describe coverage and remaining cache writes. |
docs/manual-testing.md |
Document manual preview checks. |
crates/rocm-dash-tui/src/agent/tools.rs |
Correct the comment describing tool side effects. |
apps/rocm/src/therock/python.rs |
Separate discovery from preparation and test preview decisions. |
apps/rocm/src/therock.rs |
Use planned Python information in SDK previews. |
Review effort: Balanced
| assert!( | ||
| !paths.cache_dir.join("tools").exists(), | ||
| "the preview downloaded a tool archive into {}", | ||
| paths.cache_dir.join("tools").display() |
There was a problem hiding this comment.
Fixed in 1936bce: the runtime-22 Given now points the cache at a per-scenario scratch folder (ROCM_CLI_CACHE_DIR), so a warm shared cache can no longer fail it; I reproduced the CI failure by planting a leftover archive in the shared cache and the fixed scenarios pass in that setup. The recording uv log is the evidence that nothing was prepared, and the cache check is a backstop (794081a).
… refuse With ROCM_CLI_MANAGED_PYTHON_VERSION naming a version whose wheel tag is not the one the layout needs (3.13 against cp312), a dry run on a host with no usable Python said it would prepare that Python, but a real install prepares it, finds the wrong tag and refuses. The preview now applies the same tag rule, shared with the check an existing interpreter must pass, and fails with the install's message instead. A version whose tag cannot be read off its name is announced as before. Adds a unit test and scenario runtime-23, which asserts the refusal together with the empty tools, cache and config folders. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
The harness points the CLI at a cache shared across scenarios and, on CI, across runs. runtime-22 asserted that the preview had downloaded no tool archive by looking in that shared folder, so a uv archive left by an earlier run failed it although the preview downloaded nothing. The scenarios now set ROCM_CLI_CACHE_DIR to a folder under the scenario root and assert against it. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
…y run The dry run's check of ROCM_CLI_MANAGED_PYTHON_VERSION read only plain major.minor versions, so 3.13t, cpython-3.13 and cpython@3.13 were announced as a plan although the installed interpreter reports cp313 and the real install refuses it. The version is now read in those spellings. A version whose tag still cannot be read is announced as unchecked, in both the note and the progress line, rather than as a promise. The progress line before the search no longer says a real install would prepare a Python, since the search can end in a refusal; the line naming the Python, or the refusal, follows it. runtime-23 asserts the whole output: no promise before the refusal. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
…evidence ROCM_CLI_UV_BINARY makes the CLI return the recording uv before any download could begin, so the check for a tool archive in the scenario's cache cannot fail by itself. It stays as a backstop and says so; the scenario comments and docs/testing.md now name the uv log as what shows nothing was prepared. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
Signed-off-by: Jussi Elo <jussi.elo@amd.com> # Conflicts: # apps/rocm/src/therock.rs # tests/e2e-cucumber/features/runtime_setup.feature
Signed-off-by: Jussi Elo <jussi.elo@amd.com>
Signed-off-by: Jussi Elo <jussi.elo@amd.com>
There was a problem hiding this comment.
🟡 Changes recommended
Python previews claim preparation before policy and version checks pass, while testing docs omit ROCm 10 metadata resolution’s possible managed-uv download and tools-directory creation.
3 open issues
1 resolved since last review
Review effort: Balanced
| The preview also prepares no Python. On a host with no usable Python, a real | ||
| install downloads a managed `uv`, has it install Python, and saves that Python's | ||
| path in the config; `--dry-run` instead prints a `python_note:` line saying so | ||
| and does none of it. Scenario `runtime-23` checks the note against the state it |
There was a problem hiding this comment.
Confirmed: ROCm 10 previews call ensure_uv_binary through resolve_published_pip_package_versions to run uv pip compile, and avoiding that would need a different resolver, so it is not a small behaviour-preserving change. 72551d7 limits the no-uv / no-tools wording to the canonical layout in docs/testing.md, docs/manual-testing.md, the scenario comment, the tool-group comment and the PR body; a ROCm 10 preview still prepares no Python.
…sion With an unusable PATH Python, no usable saved interpreter and managed Python disabled, the preview said a real install would use managed Python and then refused; the saved-interpreter line made the same premature claim. The two lines now state only what was found. The one line that predicts the preparation is built from the decision itself and printed by python_for_install, so it exists only for a decision that passed the bootstrap policy and the version check; the Using lines moved there with it. A real install prints exactly what it did. Adds a state-matrix unit test over PATH Python, saved Python, bootstrap policy and version override that asserts each row's whole output and the state left behind, and scenario runtime-25 for the policy refusal. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
A preview of a ROCm 10 or newer wheel resolves its package versions with uv pip compile, so on a clean host it can still download the managed uv and create the data folder's tools folder, although it prepares no Python. docs/testing.md, docs/manual-testing.md and the dash-TUI tool-group comment now say so, and describe the state matrix and the refusal scenarios the preview is checked by. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
There was a problem hiding this comment.
🔵 Needs a closer look
A clean-host run with real uv is still needed to confirm ROCm 10 previews work without provisioning Python, since the new no-Python tests cover only the canonical layout.
2 open issues
1 resolved since last review
Review effort: Balanced
juhovainio
left a comment
There was a problem hiding this comment.
Thanks for the work here — the Python download and config write are gone from the preview, and CI is green. Requesting changes on one scope point, plus a few smaller notes.
Needs resolution
- ROCMAI-867's acceptance says "no download of Python or uv". A ROCm 10+ preview can still fetch the managed
uv(and createtools/) viauv pip compile. The PR discloses this but is titled/linked as "Fixes ROCMAI-867". Either finish the work (no uv fetch in the preview) or narrow the "Fixes" to what is actually fixed.
Non-blocking
- The doc comment on
python_tag_for_versionsays a wildcard yieldsNone, but the unit test asserts"3.13.*"returnscp313(inline comment). - The hand-rolled version reader can drift from what uv accepts. The failure is benign (the version is announced as unchecked, not refused), but worth a comment saying so.
- At ~1800 lines this is large for a fix, and the version-override refusal (runtime-24/25) goes beyond the ticket. Splitting it out would make review easier.
- Windows runs none of the dry-run decision tests (
cfg(unix)tests,@requires-os:linuxscenarios). Disclosed, noted for the record. update_checkstill prepares a managed Python (disclosed).docs/llm-tool-use.mdand the skill text are not touched; please confirm they don't describe the old preview behaviour.- Minor:
tag_checkedlooks speculative,note()andprogress_text()duplicate wording, andsourceis a bare string.
CI is all green on 72551d7.
| /// `uv` accepts for a CPython release that name a major and a minor: `3.13`, | ||
| /// `3.13.1`, `3.13t` (free-threaded, which still reports `cp313`), and any of | ||
| /// those after `cpython-` or `cpython@`. Anything else (another implementation, | ||
| /// a range, a wildcard, a pre-release spelled on the minor) yields `None`. |
There was a problem hiding this comment.
This says a wildcard yields None, but the unit test asserts "3.13.*" -> cp313 (the patch part isn't validated). Please reword the comment to match the behavior (or tighten the parser if None was intended).


Summary
rocm install sdk --dry-runis meant to be a read-only preview, and the assistant'sinstall_sdk_dry_runtool is listed among the read-only tools. On a host where no usable Python can be found (a minimal container, a fresh Windows install), it was not read-only: the preview went and prepared a managed Python before printing any plan. That meant downloading a manageduv, havinguvinstall Python, and saving that Python's path in the CLI configuration. The preview needs a Python only to learn which wheel tags to select packages for, so none of that had to happen. Now the preview reports that a real install would prepare a managed Python, says so in apython_note:line, and prepares no Python and saves no Python path. A real install is unchanged. One qualification: the no-uv, no-tools-folder part holds for the canonical layout only, because a preview of a ROCm 10 or newer wheel resolves its package versions withuv pip compileand can still fetch the manageduvon a clean host (see the notes). Fixes ROCMAI-867.Reproduction
The ticket described this from reading the code and had not reproduced it. I reproduced it through the end-to-end harness against a build of
origin/main. The scenario serves one canonical release pip index from a loopback fixture, pointsPATHat a single empty folder (so no Python is found) withROCM_CLI_PYTHONunset, and pointsROCM_CLI_UV_BINARYat a recording stand-in foruvso the attempt is observable without the network. Runningrocm install sdk --channel release --format wheel --family gfx1200 --dry-runon that build printedPreparing Python 3.12...,Installing Python 3.12 via uv...andFinding Python 3.12...and exited 1 (the stand-inuvfinds no Python), so the dry run had called theuv python installstep. The path isinstall_wheel_runtimeinapps/rocm/src/therock.rscallingresolve_python_launcherbefore itsdry_runreturn, which falls through toensure_managed_pythoninapps/rocm/src/therock/python.rs. The assistant tool, the MCP tool androcm install sdk --dry-runall reach that same function, so one fix covers them.What I did not demonstrate: a real download of
uvand Python from the internet (the stand-inuvkeeps the run offline, so I observed the call to it, not bytes arriving), and a Windows run.After the fix the same scenario, a canonical-layout preview, passes: the preview exits 0 with a plan, the recording
uvis never invoked (its log does not exist, which is the evidence that nothing was prepared), no managedtoolsfolder exists under the data folder, and noconfig.jsonwas written. The scenario also looks for a tool archive in its own cache folder, but only as a backstop: withROCM_CLI_UV_BINARYset the CLI returns the stand-in before any download could start, so that folder stays empty either way and is not the evidence.Fix
resolve_python_launcher_inis split at the point where it decides to prepare a managed Python. The search itself (an explicitROCM_CLI_PYTHON, thePATHsearch, the saved managed Python, the bootstrap-disabled refusal) moves unchanged intofind_python_launcher_in, which returns either a usable interpreter orNeedsManagedBootstrap. The real resolver callsensure_managed_pythonon the second answer exactly as before. A newplan_python_launcherreports it asPlannedPython::WouldPrepareManagedinstead, andpython_for_installis the one place the wheel install chooses between the two based ondry_run. Every error the real resolution raises before the bootstrap step is raised for the preview as well, so a preview does not print a plan on a host where the install would refuse.That includes a managed-Python version override.
ROCM_CLI_MANAGED_PYTHON_VERSIONcan name a version whose wheel tag is not the layout's (for example 3.13 against the canonical cp312 layout); a real install prepares that Python and then refuses it, so the preview fails with the same message (Python wheel tag cp313 is not supported by this ROCm install; cp312 is required) instead of announcing a plan. The comparison is one shared function,ensure_python_tag_matches, used both for an existing interpreter and for the version the preview would prepare. The tag is read off the version the wayuvaccepts it for a CPython release:3.13,3.13.1, the free-threaded3.13t, and any of those aftercpython-orcpython@.uvdoes the real parsing and this repo has no parser of its own to share, so this reader is deliberately small. A version whose tag cannot be read off its name (another implementation such aspypy@3.10, a range, a pre-release spelled on the minor) is announced as unchecked: the note and the progress line say its wheel tag cannot be checked until it is installed and what the install requires, rather than promising it.For wheel selection the preview uses the interpreter tag the layout requires (cp312 for the canonical layout, cp314 for ROCm 10 and newer), because a real install refuses any other interpreter. The printed command shows
uv venv --python 3.12 ...since the real path does not exist yet. The preview output gainspython_note:only when a Python would be prepared,python_launcher:readsnot prepared by this previewin that case, andpython_source:ismanaged. Hosts that have a usable Python print exactly what they printed before.Every progress line the preview prints about Python is derived from the same decision the install takes, so a line that says what a real install would do cannot appear on a path that ends in a refusal. The opening line, which said
if needed, ROCm CLI will prepare Python X, readsChecking Python for the ROCm install.in a preview and promises nothing. The two lines about an unusablePATHPython and an unusable saved managed Python used to saya real install would use ROCm CLI's managed Pythonanda real install would prepare Python againin a preview, which was premature: bootstrap can be disabled, or the version override refused, after them. In a preview they now state only the finding (Python from PATH is not usable for this ROCm install (cp312 is required)., and the same for the saved Python). The single line that predicts the preparation,No usable Python found; a real install would prepare Python X. This preview does not., comes fromPlannedPython::progress_text, which exists only for a decision that already passed the bootstrap policy and the version check;python_for_installprints it, together with theUsing ...lines of the other outcomes, which moved there fromtherock.rsso the whole Python output is one value that a unit test can capture. A real install prints all of its lines exactly as before, so non-dry-run output is byte-identical.The dash-TUI tool-group comment in
crates/rocm-dash-tui/src/agent/tools.rslisted this side effect forinstall_sdk_dry_run; it now saysupdate_checkcan still prepare a Python, and that a ROCm 10 preview can still fetch the manageduv.Verification
Ran, on Linux with a private target dir, on the final tree:
cargo fmt --all --check;cargo clippy -p rocm -p e2e-cucumber --all-targets -- -D warnings;cargo test -p rocm --bin rocm therock::; thefeature_namingguard;cargo xtask manifest --check;cargo xtask check-crate-edges;cargo xtask lychee; the leak scan againstorigin/main...HEAD. The scenarios were run through the repo's harness withROCM_CLI_BINARYpointing at a debug build of the branch (equivalent for these scenarios to the binarycargo xtask e2ebuilds), once with a leftover tool archive planted in the shared cache folder to mimic CI's reused cache. Earlier in the PR's life the wholecargo test -p rocmpassed (1474 tests, against 1467 on main at the time, before the later rounds added more unit tests) andruntime-23was run against the unfixed build, where it fails.Mutations, each reverted by a targeted edit afterwards: making
python_for_installignoredry_runturnsa_dry_run_with_no_usable_python_prepares_nothinganda_dry_run_with_an_unusable_saved_python_prepares_nothingred; making the preview save a Python path in the config turns the same two red and fails theruntime-23Then step with the config path in the message; passingfalseinstead ofdry_runat the call site intherock.rsturnsruntime-23red at theWhenstep withInstalling Python 3.12 via uv...in the output; skipping the tag check in the preview turnsa_dry_run_refuses_a_managed_python_version_the_install_would_refusered; removing thetsuffix handling, and separately thecpython-andcpython@prefix handling, from the version reader turns botha_managed_python_version_is_read_as_the_wheel_tag_it_reportsanda_dry_run_reads_every_spelling_of_a_managed_python_version_the_same_wayred each time; restoring the promising opening progress line in a preview turnsruntime-24red; restoring the prematurea real install would use ROCm CLI's managed Pythontext on thePATHline, then on the saved-Python line, and printing the prediction before the version check each turn the state-matrix testa_dry_runs_whole_python_output_follows_one_decision_in_every_statered, naming the row; asserting against the shared cache folder again, with a leftover archive planted in it, reproduces the CI failureruntime-23once had.Not run: clippy for
x86_64-pc-windows-msvc(theaws-lc-sysbuild script fails to cross-compile on this host, an environment limit independent of this change; the changed non-test code has nocfg(windows)branches), the full workspace gate andscripts/smoke_local.pyon the final tree, and a real network download ofuvand Python.User-visible behaviour
Scenario
runtime-23(runtime-install-preview-prepares-no-python) covers the preview on a host with no usable Python,runtime-24(runtime-install-preview-refuses-unusable-managed-python) the version-override refusal, andruntime-25(runtime-install-preview-refuses-disabled-managed-python) the policy refusal when the host's only Python is unusable and managed Python is disabled, all intests/e2e-cucumber/features/runtime_setup.feature. All three are@requires-os:linuxand need no GPU or network, so they run on the ordinary hermetic e2e lane. The empty-PATHhost is built for the Linux search order; the decisions themselves are unit-tested on Unix hosts, and the Windows search order is not covered by a test.runtime-23's final step asserts the preview's own claim (thepython_note:line and theThis preview does not.progress line) together with the state it left, with the recordinguv's log as the evidence nothing was prepared.runtime-25asserts that the output holds the neutral finding about thePATHPython, the same refusal as the install, no line predicting anything, and no side effects.runtime-24asserts the final output as a whole: the neutral opening line, none of the promises the refusal would contradict (would prepare Python 3.13,ROCm CLI will prepare, a printed plan), the refusal text on stderr, and the same absence of side effects. Both give the CLI a cache folder of their own (ROCM_CLI_CACHE_DIRunder the scenario root) because the harness otherwise shares one cache across scenarios and CI runs, where a leftover archive would trip the backstop check.fake-uvgains an opt-inFAKE_UV_LOGfile so a scenario can showuvwas never run.The unit test
a_dry_runs_whole_python_output_follows_one_decision_in_every_stateis the state matrix: every combination ofPATHPython (usable, unusable, absent), saved Python (usable, unusable, absent), bootstrap policy (enabled, disabled) and version override (default, accepted patch release, unreadable, refused tag), 72 rows, each asserting the complete progress output against an independently written expectation, that no prediction line exists unless the row ends in the one promise, and thatuvwas not run and no file or config was written. Other boundary cases are unit-tested inapps/rocm/src/therock/python.rstoo: no Python and no config, data or cache folder at all (nothing is created); a usable Python on the search path; a usable saved managed Python (the manifest and config are left untouched); an unusable saved managed Python (reported as needing a Python, record left alone); managed bootstrap disabled (preview and install refuse with the same message); a version override that the install would refuse, in the spellings3.13,3.13t,cpython-3.13andcpython@3.13; patch andcpythonspellings of the accepted minor; and an unreadable spelling announced as unchecked.docs/testing.mdanddocs/manual-testing.mddescribe the preview behaviour and the manual check. The--helptext and README did not claim anything this change makes false, so they are unchanged.Notes for reviewers
The preview still writes the package-index metadata cache under the cache folder (the ticket mentions it): that is an HTTP cache of the index the plan is resolved from, and
docs/testing.mdsays so. I left it alone rather than thread a no-write mode through the resolver. A preview of a ROCm 10 or newer wheel also still callsensure_uv_binarythroughresolve_published_pip_package_versions, because it resolves the published versions withuv pip compile; on a clean host that can download the manageduvand create the data folder'stoolsfolder, although no Python is prepared. Avoiding it would mean a different resolver for the preview, which is not a small behaviour-preserving change, sodocs/testing.md,docs/manual-testing.md, the scenario comments and the tool-group comment now limit the no-uvguarantee to the canonical layout. Theupdate_checktool androcm update --checkresolve Python through the sameresolve_python_launcherintherock/update.rsand can still prepare a managed Python on such a host; that is a sibling of this bug, outside what the ticket asks for, and the corrected tool-group comment says so. It is a natural follow-up using the sameplan_python_launcherseam. A preview that uses an existing Python still runs that interpreter and its-m venvprobe (in a temporary folder that is removed), as it always did.Reading order:
therock/python.rs(the split,PlannedPythonand the version reader), then theinstall_wheel_runtimehunks intherock.rs, then the scenarios and their steps. Open PRs touching the same code: #563 and #568 both edit theuv_venv_args(&python_launcher.executable, ...)line in the dry-run block ofinstall_wheel_runtime, which this change replaces withpython.venv_python_arg(); the textual conflict is one line and either resolution is mechanical. #569 changesensure_uv_venvon the real-install path only. #820 and #819 edit other parts oftherock.rsand the same feature and step files, so expect trivial hunk conflicts. #901 and #812 touchagent/tools.rselsewhere than the comment block edited here. Another scenario namedruntime-22landed on main while this was open, so these two are numberedruntime-23andruntime-24.