Retry install_pip_pkg with --ignore-installed after an unrecordable uninstall - #6485
jantonguirao wants to merge 1 commit into
Conversation
…ninstall pip refuses to uninstall a distribution whose metadata has no RECORD file - which is how distro package managers install Python packages - and aborts the whole install instead of upgrading it. Any qa suite that needs a newer version of a package the base image preinstalled through apt/dnf can hit this; it is what happened when PR NVIDIA#6483 asked for unpinned flask and pip tried to pull blinker forward from the apt-installed 1.7.0 (see that PR's CI run 67423372, job L0_TL0_python-self-test-readers-decoders--py312--1GPU_CUDA13, which died in `pip install flask` before a single test ran). install_pip_pkg now recognizes the failure - the pip message differs across versions, so it matches all three wordings pip has used - and retries with --ignore-installed, which is what pip's own hint now recommends. The retry is not scoped to the failing package: --ignore-installed clears the resolver's whole view of installed distributions, so nothing is uninstalled and the new wheel is written over the old one. That is enough to get an install to succeed, but it is not a clean recovery - the stale dist-info survives, so anything reading installed versions afterwards (importlib.metadata, pip list) still reports the old one, and a later install needing the same upgrade hits the identical error again. Comment the retry loudly for that reason. The exit status has to go through a temp file rather than $? or ${PIPESTATUS[0]}: this function runs under callers with `set -e`, and the `if cmd1 || cmd2; then ... fi` list piped into `tee` executes in a subshell, which errexit would tear down before an assignment after the list could run. ${PIPESTATUS[0]} would work under `set -o pipefail`, which qa/test_template.sh sets today, but not for a caller that does not. install_pip_pkg is on the path of every qa suite that sources qa/test_template.sh, so this stays a narrow, behavior-preserving addition: the happy path is unchanged, and a failure unrelated to an unrecordable uninstall still propagates and aborts the caller exactly as before. Verified against real pip (22.0.2 and 26.2.1) with a blinker install whose RECORD file was removed to reproduce the apt layout: the genuine failure recovers and returns 0, an unrelated failure (installing a nonexistent package) still aborts the caller, and the happy path is untouched - all three checked both with and without `set -o pipefail`. Signed-off-by: Joaquin Anton Guirao <janton@nvidia.com>
|
| if [ "${ret}" != "0" ] && grep -qE "${no_record_re}" "${pip_log}"; then | ||
| # this is what pip itself now suggests for this error; it leaves the stale .dist-info | ||
| # behind, so anything introspecting installed versions afterwards may see the old one | ||
| echo "pip cannot uninstall a preinstalled package, retrying with --ignore-installed" | ||
| if ${install_cmd} --ignore-installed --no-index || ${install_cmd} --ignore-installed; then | ||
| ret=0 | ||
| else | ||
| ret=$? | ||
| fi |
There was a problem hiding this comment.
The new retry branch has no automated regression test. Existing main-branch callers do not exercise a distribution without a RECORD file, so changes to diagnostic matching, status capture, or unrelated-failure propagation could break this recovery without CI detecting it. A shell test using a fake pip command should cover both successful recovery and nonmatching failure propagation.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
🔵 Needs a closer look
Cleanup must handle stale metadata left by the fallback.
Pull request overview
This PR updates shared QA pip installation to recover upgrades blocked by missing RECORD metadata.
Changes:
- Captures pip output and status.
- Detects known unrecordable-uninstall failures.
- Retries with
--ignore-installedwhile preserving unrelated failures.
File summaries
| File | Summary |
|---|---|
qa/test_template_impl.sh |
Adds fallback retry logic; cleanup may still fail on stale RECORD-less metadata. |
Review details
Suppressed comments (1)
qa/test_template_impl.sh:61
- This retry can leave a stale RECORD-less
.dist-infofor a package that is itself inpip_packages. The common cleanup later in this file runspip uninstall -y $removewithout any recovery, andsetup_packages.pyincludes direct package entries in that removal list, so a direct distro-installed package can make the suite fail after the tests have passed. Please either clean/replace the stale metadata before returning success or make cleanup explicitly handle packages that required this fallback.
if ${install_cmd} --ignore-installed --no-index || ${install_cmd} --ignore-installed; then
- Files reviewed: 1/1 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
CI MESSAGE: [69108883]: BUILD STARTED |
|
CI MESSAGE: [69108883]: BUILD FAILED |
|
CI MESSAGE: [69108883]: BUILD PASSED |
Category:
Other (Refactoring)
Description:
install_pip_pkginqa/test_template_impl.shtries the offline/pip-packagesmirror, thenfalls back to the index. Neither branch survives pip's
uninstall-no-record-fileerror: piprefuses to uninstall a distribution whose metadata has no
RECORDfile - which is how distropackage managers install Python packages - and aborts the whole command instead of upgrading it.
This is what happened to #6483's CI run: it asked for unpinned
flask, pip resolvedflask==3.1.3,which needs
blinker>=1.9, and the base image's apt-installedblinker==1.7.0has noRECORD, sothe install died before a single test ran. That PR worked around it with a version pin
(
flask==3.0.3, which is satisfied by the preinstalledblinkerand never triggers an upgrade),per JanuszL's suggestion on that thread
to fix the underlying installer logic instead.
install_pip_pkgnow recognizes the failure - the pip message differs across versions, so itmatches all three wordings pip has used - and retries with
--ignore-installed, which is whatpip's own hint now recommends for this exact error. Since
install_pip_pkgis on the path of everyqa suite that sources
qa/test_template.sh, the change stays narrow: the happy path is unchanged,and a genuine failure unrelated to an unrecordable uninstall still propagates and aborts the caller
exactly as before.
Not a clean recovery, on purpose left as-is:
--ignore-installedis not scoped to the failing package - it clears the resolver's whole view ofinstalled distributions, so nothing is uninstalled and the new wheel is written over the old one.
That is enough to make the install succeed, but the stale
.dist-infosurvives, soimportlib.metadata/pip liststill report the old version afterwards, and a later install needingthe same upgrade hits the identical error again. The retry is commented loudly for this reason.
Sequencing with #6483:
This does not touch
qa/TL0_python-self-test-readers-decoders/test_nofw.sh- theflask==3.0.3pinlives on #6483's branch, which has not merged yet, so there is nothing here to relax. Once both land,
relaxing that pin to
flask<4(keeping an upper bound becausemotois pinned at5.2.3andmoto.serversits on flask + flask-cors) would be a one-line follow-up that also gives this path itsfirst real CI exercise, since nothing on
maintoday hits an unrecordable uninstall.Affected modules and functionalities:
qa/test_template_impl.sh:install_pip_pkg().Key points relevant for the review:
qa/(the per-package loop, the CUDAwheel install,
TL0_python-self-test-core'snumpy<2downgrade,TL1_tensorflow-dali_test) getsthe fallback for free.
$?/${PIPESTATUS[0]}: theiflist is piped into
teeto keep streaming output into the test log, which runs it in a subshell;under the
set -eevery caller of this function has, a non-zero status inside that subshell wouldtear it down before an assignment after the list could run.
${PIPESTATUS[0]}would work underset -o pipefail, whichqa/test_template.shsets today, but not for a caller that does not.Tests:
Verified against real pip (22.0.2 and 26.2.1), with
blinker'sRECORDfile removed to reproducethe apt/dnf layout, both with and without
set -o pipefail:flask==3.1.0install that trips the RECORD-lessblinkerupgrade: recovers, returns 0Checklist
Documentation
DALI team only
Requirements
REQ IDs: N/A
JIRA TASK: N/A