Repository navigation
fix(install): preview a ROCm 10 SDK install without fetching uv - #954
Open
jussielo-amd wants to merge 8 commits into
Open
jussielo-amd wants to merge 8 commits into
jussielo-amd wants to merge 8 commits into
Conversation
ensure_uv_binary returns the binary named by ROCM_CLI_UV_BINARY or the managed copy, and otherwise takes a lock under the data folder's tools folder and downloads one. A caller that must leave the machine untouched (an install preview) had no way to ask whether a uv is already there. find_uv_binary answers that question and nothing more: the override, or the managed copy when it runs, and None when ensure_uv_binary would have had to fetch. It creates no folder and takes no lock. The managed copy's location now comes from one helper shared with the install, and a test shows the lookup finds the copy the install reuses. No caller yet; ensure_uv_binary behaves exactly as before. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
install_wheel_runtime wrote the sdk install plan inline, from the provenance lines down to the dry-run command and manifest path. Move those writes into therock/wheel_plan.rs behind a WheelPlan value: write_wheel_plan for the lines a preview and a real install share, write_dry_run_tail for the lines only a preview adds. The lines, their order and their wording are unchanged; the existing tests pass as they were. This lets the next change print a plan whose package versions are not chosen yet through the same writer instead of a second copy of it. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
… present A preview of a ROCm 10 or newer wheel has uv pip compile choose the torch and torchvision versions, and the plan's package specs, runtime key and command are made of them. Add runtime-27: with a uv present (the recording stand-in named by ROCM_CLI_UV_BINARY, answering the compile with the fixture's four pins) the preview runs it once, for the compile and nothing else, prints the resolved plan and reports no skipped step. It passes on the current code and is the half of the contract a later change must keep when a preview stops fetching uv on a host that has none. The stand-in gains an opt-in FAKE_UV_DRAIN_STDIN, because the CLI writes the requirements to uv's stdin before it reads the answer and a stand-in that exits without reading can fail that write. The two host setups that put an empty PATH and a cache folder of their own in the scenario now share one helper. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
On the ROCm 10 and newer wheel layout, rocm install sdk --dry-run (and the install_sdk_dry_run assistant tool) resolved the torch and torchvision versions with uv pip compile through the same resolver a real install uses, which acquires the managed uv: on a clean host the preview downloaded uv and created the data folder's tools folder although it promises to change nothing. The canonical layout needs no uv and was not affected. The resolver now takes a UvAccess. Acquire is what a real install and rocm update pass, and they decide and call exactly what they did before. PresentOnly is the preview's: it runs a uv that is already there (the binary ROCM_CLI_UV_BINARY names, or the managed copy a previous install left) and, with none, stops before the compile and returns an UnresolvedPipRuntime holding what needs no uv (the ROCm version, the torchaudio pin, the requirements uv would be given) and no package versions. The preview prints that plan through the same writer as every other plan, with target, runtime_key, package_specs, command and manifest marked not resolved by this preview, and says why in a resolution_note line and a progress line, in the voice of python_note. It cannot say whether the published stack installs together either, and the note says that. The decision is one pure function, tested over both layouts, both accesses and a uv present or not; a second test runs the real resolution against a served index and a stand-in uv and counts what each row looked for, acquired and ran. Scenario runtime-28 asserts the note together with the state on a host with no uv: no tools folder, no tool archive, no saved config and no torch or torchvision version in the output. The previews in therock-next-02, -10, -11 and -13 that print resolved specs start from an installed uv. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
The previous commit took therock/resolve/wheel.rs past the 1500 production-line limit. Move the part that decides who may fetch uv (UvAccess, UvPolicy, VersionResolution, UvChoice, plan_version_resolution and uv_for_compile) and its state-matrix tests into therock/resolve/uv_access.rs. It builds only on SourceLayout, so wheel.rs keeps acting on the answer and nothing else changes: the code and the tests are moved as they were, with the items widened to pub(super) so wheel.rs can name them. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
The notes written when the preview stopped preparing Python limited the no-uv, no-tools-folder guarantee to the canonical layout, because a ROCm 10 preview could still download the managed uv. It no longer does, so make every place that called the preview read-only or described its uv behaviour say what is now true: the --dry-run help, the README's ROCm 10 section, docs/testing.md and docs/manual-testing.md (including the new scenarios and the one gap no test reaches without a network), and the dash-TUI tool-group comment for install_sdk_dry_run. The assistant tool descriptions, docs/llm-tool-use.md and the assistant skill make no claim about the preview's uv handling, so they are unchanged. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
Clippy under -D warnings flags a function that can be const, a test helper that appends a format! to a String, and an import that only the tests use since the decision moved to uv_access.rs. Fix the three; no behaviour changes. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
jussielo-amd
marked this pull request as ready for review
November 6, 2026 21:10
jussielo-amd
requested review from
volen-silo
and
a balanced review from Copilot
November 6, 2026 21:10
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
The testing documentation recommends the unsupported rocm update --check command, which fails before resolution and should be replaced with the default rocm update check.
1 open issue
What changed in this PR
This completes the SDK preview work from #906 by preventing ROCm 10+ dry runs from downloading managed uv. Previews reuse an available binary or clearly report unresolved values, while real installs and update resolution retain their existing behavior.
Changes:
- Add lookup-only
uvaccess and distinguish preview resolution from installation. - Share plan rendering for resolved and unresolved package versions.
- Add regression coverage and update help, documentation, and tool-side-effect comments.
| File | Description |
|---|---|
| tests/e2e-cucumber/tests/e2e/therock_steps.rs | Add preview setup and output/state assertions. |
| tests/e2e-cucumber/src/bin/fake-uv.rs | Optionally drain stdin for reliable compile simulation. |
| tests/e2e-cucumber/features/therock_next_generation.feature | Explicitly prepare uv for resolved-plan scenarios. |
| tests/e2e-cucumber/features/runtime_setup.feature | Cover previews with and without available uv. |
| README.md | Explain partial previews when uv is absent. |
| docs/testing.md | Document coverage and verification limits. |
| docs/manual-testing.md | Update expected preview behavior. |
| docs/architecture.md | Document new modules and resolution flow. |
| crates/rocm-dash-tui/src/agent/tools.rs | Clarify preview side effects in comments only. |
| crates/rocm-core/src/uv.rs | Add non-acquiring lookup and focused tests. |
| crates/rocm-core/src/lib.rs | Export find_uv_binary. |
| apps/rocm/src/therock/wheel_plan.rs | Centralize resolved and unresolved plan rendering. |
| apps/rocm/src/therock/resolve/wheel.rs | Implement preview-aware resolution and regression tests. |
| apps/rocm/src/therock/resolve/uv_access.rs | Define and test uv access decisions. |
| apps/rocm/src/therock/resolve.rs | Register and expose new resolution components. |
| apps/rocm/src/therock.rs | Route dry runs through preview resolution. |
| apps/rocm/src/cli.rs | Describe the no-download behavior in dry-run help. |
Review effort: Balanced
docs/testing.md said rocm update --check does not preview in the install-preview sense, but rocm update has no --check flag: the plain rocm update (without --apply) is the check. The advised-commands test rejects the spelling. Name the update_check tool and the plain rocm update check instead. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
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.

Closes ROCMAI-896.
Summary
rocm install sdk --dry-run(and the assistant'sinstall_sdk_dry_runtool, which reaches the same code) promises to change nothing. #906 made that true for Python. This finishes the other half: for a ROCm 10 or newer wheel the preview resolved the package versions withuv pip compile, through the same resolver a real install uses, and that resolver acquires the manageduv. On a clean host the preview therefore downloadeduvand created the data folder'stoolsfolder before printing its plan. The canonical layout needs nouvand was not affected.Before: on a host with no
uv,rocm install sdk --version 10.0.0 --family gfx1200 --dry-runprintedDownloading uv (latest) from https://github.com/astral-sh/uv/releases/latest/download/...on stderr and put the archive in the cache'stools/uv/latestfolder (reproduction below); the lock taken before that download is what creates the data folder'stoolsfolder, which the mutation run below shows. After: it downloads nothing and creates nothing. If auvis already there (the binaryROCM_CLI_UV_BINARYnames, or the managed copy any earlier install left) the preview runs it and prints exactly the complete plan it printed before. If none is there it prints what it can know without one and says what it did not do. A real install androcm updatestill acquireuvexactly as before.I chose option B from the ticket: resolve without fetching, using a
uvthat is already present, and when none is present skip the resolution and say so. Onlyuv pip compileproduces thetorchandtorchvisionversions (the CLI picks the ROCm version and the torchaudio pin), and the package specs, the runtime key (a hash of the specs), the install folder, the manifest and the printed command are all made of them. So a preview that skips the compile cannot print those lines, and it cannot say whether the published stack is jointly installable either; it must not print a guess for them. Option A (always print the unresolved lines) would have changed the plan for everyone, including the hosts where auvis present and the complete plan costs nothing; B keeps the complete plan wherever it is free and degrades only where completing it would need the network and a write to the data folder. Option C (keep the fetch and document it as an exception to the tools being read-only) is the smallest change but leaves a model-callable tool in the read-only group that downloads a binary, and every statement that calls the preview read-only would need an asterisk. The cost of B is real: on a clean host a ROCm 10 preview is a partial plan untiluvhas been fetched once, which any real install (or any command that prepares Python or ComfyUI) does.What the preview now prints on a host with no
uv, in the voice of the existingpython_note:line. A progress line:No uv is installed; a real install would fetch it and resolve the torch and torchvision versions with `uv pip compile`. This preview does not.And in the plan, afterpython_note::resolution_note: no uv is installed, so a real install would fetch it and run `uv pip compile` to choose the torch and torchvision versions and to check that the published packages install together, which this preview did not do; the package specs, runtime key, install folder, manifest and command depend on those versions and are not resolved here.target,runtime_key,package_specs,commandandmanifestreadnot resolved by this preview(targetis the folder after--prefix, which does not depend on the versions).version_resolution_specs,torchaudio_selection,device_target,selected_rocm_versionand the rest are printed as before. No torch or torchvision version appears anywhere in the output.What changed where
find_uv_binaryisensure_uv_binarystopped short of its download: the override, or the managed copy when it runs, otherwiseNone. It takes no lock and creates no folder. The managed copy's location comes from one helper shared with the install, and a test shows the lookup finds the copy the install reuses.UvAccess(Acquirefor a real install androcm update,PresentOnlyfor a preview) andplan_version_resolution, a pure function of the layout, the access and whether auvis present.Acquirenever asks whether one is present and never skips, so what a real install and an update decide and call is what they always did.resolve_pip_runtimeandresolve_pip_runtime_with_timeoutareAcquire, and return a resolved plan or an error. The newpreview_pip_runtimeisPresentOnlyand may returnPipRuntimePlan::NeedsUv, holding anUnresolvedPipRuntime: the ROCm version, the torchaudio pin, the requirement linesuvwould be given, and no package versions, so nothing made from them can be built from it by accident.resolve_published_pip_package_versionsnow takes theuvto run instead of acquiring it. The uv-access part was moved out into its own file in a separate commit because the previous one took this file past the module-size limit.install_wheel_runtimein a pure move first): one writer for thesdk installplan.PlanStack::ResolvedorPlanStack::NeedsUvis the only place a plan differs, so a line the plan gains appears in both; a test holds the two key sequences equal.install_wheel_runtimeaskspreview_pip_runtimewhen it is a preview.--dry-runhelp, the README's ROCm 10 section, testing.md, manual-testing.md, architecture.md and the dash-TUI tool-group comment in tools.rs, which said a ROCm 10 preview could still fetchuv, now say it does not. I grepped the other places that call the preview read-only: the assistant tool descriptions,docs/llm-tool-use.mdand the assistant skill make no claim about the preview'suvhandling, so they are unchanged. The skill is compiled in withinclude_str!and was not touched.Test plan
Reproduction, before the fix, on a clean data folder. Scenario
runtime-28(below) runsrocm install sdk --channel release --format wheel --family gfx1200 --version 10.0.0 --dry-runagainst a loopback ROCm 10 index with nouvanywhere (noROCM_CLI_UV_BINARY, empty scenario data folder, its own cache folder). I ran it against a debug build oforigin/mainwith the scenario already in place. It failed at the When step: stderr beganDownloading uv (latest) from https://github.com/astral-sh/uv/releases/latest/download/uv-x86_64-unknown-linux-gnu.tar.gz, the download reached the scenario's cache and then failed only because the scenario'sPATHis empty (failed to extract uv archive .../scratch-cache/tools/uv/latest/uv-x86_64-unknown-linux-gnu.tar.gz...failed to launch tar). So the managed-uvfetch was reached, over the real network, from a preview. The same scenario passes on the fixed build. What I did not do: observe a successful full download and extraction (the emptyPATHstops it) and any Windows run.Ran, on the final tree (Linux, one private target dir):
cargo test -p rocm --bin rocm therock: 234 passed, 1 ignored (228 before this change plus 6 new). The leaf-name list ofcargo test -p rocm --bin rocm -- --listis 1683 names onorigin/mainand 1689 here; none disappeared, the six added are the new tests below.cargo test -p rocm-core --lib uv::: 36 passed, 2 ignored (three of them new here, two Unix-only).cargo fmt --all --check;cargo clippy --locked -p rocm --all-targets -- -D warnings;cargo clippy --locked -p e2e-cucumber --test e2e -- -D warnings;cargo clippy --locked -p rocm-core --all-targets -- -D warnings; the same forrocmandrocm-coreagainstx86_64-pc-windows-msvc(with stub C compilers for the build scripts, since check and clippy never link): all exit 0. Windows because the new tests carry Unix-only parts.cargo xtask manifest --check,cargo xtask check-crate-edges,cargo xtask check-architecture-doc(all 0),cargo test -p e2e-cucumber --test feature_naming(4 passed),cargo xtask lychee(0).cargo xtask check-module-sizereports onlyservice_launch.rs, which is over onorigin/maintoo.runtime-23toruntime-28andtherock-next-01to-05,-08and-10to-15against a debug build withrocm/e2e-test-hooks: 18 scenarios passed, run through the real harness withROCM_CLI_BINARYset.therock-next-02,-10,-11and-13need the network, as they did, to install the manageduv.origin/main: the only hits are the wordsprivatein existing architecture entries andinternal errorin a new refusal message (the same phrasePlannedPython::into_launcheruses).Not run: the full workspace test gate,
scripts/smoke_local.py, the GPU scenarios (therock-next-07and-09fail onorigin/maintoo on a host with no GPU),cargo test -p rocm-dash-tui(comment-only change there; the crate is compiled by the clippy runs above), and any Windows execution of the tests.State matrix. The decision is
plan_version_resolutionover layout (canonical, ROCm 10) by access (Acquire,PresentOnly) by auvpresent or not, 8 rows, each asserting the decision and how many times a presentuvwas looked for (the_version_step_is_decided_by_layout_access_and_a_present_uv_alone, runs on every platform).Skipexists for exactly one row (ROCm 10, preview, none present).a_preview_never_acquires_uv_and_a_real_resolution_still_does(Unix, because the stand-inuvis a script) runs the real resolution against a served index and asserts per row the outcome and what was touched (looked, acquired,uvruns, tools folder): ROCm 10 preview with none present stops and renders the plan in the same row, with the unresolved lines, the note, and no version onlyuvcould have chosen; ROCm 10 preview with one present runs it once and is not acquired again; ROCm 10 install or update acquires once, with or without one present, and never looks first; a failed acquire fails with the context it always had; the canonical layout never looks, acquires or runs, for either access.the_compile_step_acquires_uv_only_when_told_tocovers the injected acquire. The plan's wording is held bythe_plan_a_preview_without_uv_prints_is_exactly_this(the whole text),an_explicit_prefix_is_the_target_of_a_preview_without_uvanda_resolved_and_an_unresolved_plan_print_the_same_keys, which run on every platform. In rocm-core:looking_for_a_managed_uv_on_a_clean_host_finds_none_and_creates_nothing,an_installed_managed_uv_is_found_where_the_install_puts_itanda_managed_uv_that_does_not_run_is_not_reported.Scenarios, all in the hermetic Linux mock lane (
@requires-os:linux, no GPU; the existingtherock-nextones keep their lane):runtime-27(runtime-install-preview-of-rocm-10-uses-the-uv-that-is-present) is a characterisation scenario that passes before and after: with auvpresent (the recording stand-in answeringuv pip compilewith the fixture's four pins) the preview runs it exactly once, for the compile and nothing else, and prints the resolved plan.runtime-28(runtime-install-preview-of-rocm-10-fetches-no-uv) asserts the message and the state together: the progress line, theresolution_note:, the five unresolved lines, the known lines, no torch or torchvision version, and then no managedtoolsfolder, no tool archive in the scenario's own cache, no saved config. It is red onorigin/mainand green here.therock-next-02,-10,-11and-13, which print resolved package specs, gainAnd a uv is already installed, the precondition they always had when the preview fetcheduvfor them. The stand-inuvgains an opt-inFAKE_UV_DRAIN_STDIN, because the CLI writes the requirements touv's stdin and a stand-in that exits without reading can fail that write depending on timing. The decision and the plan text run on the Windows job too; no scenario runs a preview on Windows.Mutation evidence. Each was applied, the named tests were run and shown red, and the edit was reverted by a targeted reverse edit:
uv(PresentOnlyreturnsCompile(Acquire)):the_version_step_is_decided_by_layout_access_and_a_present_uv_aloneanda_preview_never_acquires_uv_and_a_real_resolution_still_doesred;runtime-28red.uvignored (PresentOnlyalways skips): the same two unit tests red;runtime-27red.Acquirelooks for a presentuvand skips): the same two unit tests red.uvbut still prints the skipped plan (so it succeeds):runtime-28red at its Then step withthe preview created the managed tools folder .../data/tools.runtime-28red.resolution_noteline dropped from the plan:the_plan_a_preview_without_uv_prints_is_exactly_this,a_resolved_and_an_unresolved_plan_print_the_same_keysanda_preview_never_acquires_uv_and_a_real_resolution_still_doesred.package_specsprinted instead ofnot resolved by this preview: the whole-text test and the integration test red.--prefixignored in the unresolved plan:an_explicit_prefix_is_the_target_of_a_preview_without_uvred.find_managed_uvcreating the tools folder:looking_for_a_managed_uv_on_a_clean_host_finds_none_and_creates_nothingred; without the usability check:a_managed_uv_that_does_not_run_is_not_reportedred; ignoring the version in the location:an_installed_managed_uv_is_found_where_the_install_puts_itandconcurrent_bootstraps_share_one_download_and_both_get_a_usable_uvred.One mutation is not caught: changing the single
UvAccess::Acquirethatresolve_pip_runtime_with_timeoutpasses toPresentOnly. No hermetic test can distinguish it without the network, because the only difference is whether an absentuvis downloaded.therock-next-08and-14stay green under it (I ran them), since a real install's Python step already fetcheduvbefore the resolver looks. It is held by the types: that entry point returns a resolved plan and refuses the unresolved one with an internal error, so a real install on a clean host with a usable Python would fail loudly rather than silently preview. docs/testing.md says so.Notes for reviewers
Reading order, by commit:
find_uv_binaryin rocm-core; the pure move of the plan writer (wheel_plan.rs, no behaviour change); theruntime-27characterisation scenario; the fix; the move of the access decision out ofwheel.rs; the docs; a lint fix-up. Each commit builds and passes its own tests.Sibling that this does not fix:
rocm update --apply --dry-runandupdate_checkresolve the update plan first, with the real access, so they can still fetchuv(and prepare a managed Python); that is the update path the ticket asks to leave alone, and it is the one place a ROCm 10 preview can still reachuvover the network.rocm update --apply --dry-runthen prints the complete plan, because that fetch has already happened. The preview still writes its package-index metadata cache under the cache folder, as documented.Open PRs touching the same code: #951 edits the dash-TUI tool-group comment block, tools.rs, and
therock_steps.rsandtherock.rs(the update-check sibling of #906), so expect a textual conflict in that comment, which both PRs rewrite; the resolution is to keep both statements. #820 and #819 add scenarios totherock_next_generation.featureandtherock_steps.rsand edittherock.rs; #568 edits the dry-run block that this change moves intowheel_plan.rs, so its hunk needs to be reapplied there. No open PR addsruntime-27orruntime-28.