From 069d18247feb346d02bf905f5af4085e819534cb Mon Sep 17 00:00:00 2001 From: A-G0D <143042805+A-G0D@users.noreply.github.com> Date: Wed, 4 Nov 2026 00:27:12 -0600 Subject: [PATCH] Ignore malformed lockfiles in `generate-lockfiles` `generate-lockfiles` reads the existing lockfile to plan a uv sync and to compute the dependency diff, so a malformed lockfile raised `TOMLDecodeError` even though the command was about to overwrite it. Treat an unparseable existing lockfile as absent so the lockfile is regenerated. Fixes #23706 Signed-off-by: A-G0D <143042805+A-G0D@users.noreply.github.com> --- docs/notes/2.34.x.md | 2 ++ .../pants/backend/python/goals/lockfile.py | 7 ++-- .../backend/python/goals/lockfile_test.py | 32 +++++++++++++++++++ .../python/util_rules/lockfile_diff.py | 6 ++-- 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/docs/notes/2.34.x.md b/docs/notes/2.34.x.md index 161da74f163..6abe26d2aef 100644 --- a/docs/notes/2.34.x.md +++ b/docs/notes/2.34.x.md @@ -111,6 +111,8 @@ Support for creating multiplatform/foreign platform pexes when using the `uv` re Fixed a bug that caused `generate-lockfiles --sync` not to have its intended effect when using `uv` lockfiles. +`generate-lockfiles` now overwrites an existing lockfile that fails to parse, rather than failing with a `TOMLDecodeError`. + Fixed a bug that could cause tool execution to fail when resolving the tool from a `uv` lockfile. Required named indexes to be referenced explicitly in requirement sources when using the uv resolver, to match the pex resolver's behavior. diff --git a/src/python/pants/backend/python/goals/lockfile.py b/src/python/pants/backend/python/goals/lockfile.py index 7e81a948fb8..2841a319912 100644 --- a/src/python/pants/backend/python/goals/lockfile.py +++ b/src/python/pants/backend/python/goals/lockfile.py @@ -5,6 +5,7 @@ import itertools import os.path +import tomllib from collections import defaultdict from dataclasses import dataclass from operator import itemgetter @@ -403,9 +404,9 @@ async def generate_uv_lockfile( raise ValueError( f"Expected lockfile entry to be a FileEntry but was a {type(repo_lock_entry)}" ) - except IntrinsicError: - # May fail if the file doesn't exist, which is expected the first time a new lockfile - # is generated. + except (IntrinsicError, tomllib.TOMLDecodeError): + # May fail if the file doesn't exist or is malformed, which is expected the first time + # a new lockfile is generated or when the existing one needs to be overwritten. pass uv_config = resolve_config.uv_config() diff --git a/src/python/pants/backend/python/goals/lockfile_test.py b/src/python/pants/backend/python/goals/lockfile_test.py index bf4c8d1b6dd..83c70b61f42 100644 --- a/src/python/pants/backend/python/goals/lockfile_test.py +++ b/src/python/pants/backend/python/goals/lockfile_test.py @@ -912,3 +912,35 @@ def generate_and_get_versions(reqs: FrozenOrderedSet[str], sync: bool) -> dict[s # Regenerate with --sync. versions = generate_and_get_versions(updated_requirements, sync=True) assert {"ansicolors": "1.1.8", "cowsay": "6.0"} == versions + + +def test_uv_lockfile_generation_ignores_malformed_existing_lockfile( + rule_runner: PythonRuleRunner, +) -> None: + # Regression test for https://github.com/pantsbuild/pants/issues/23706. + rule_runner.set_options( + [ + "--python-resolves={'test': 'test.lock'}", + "--python-separate-lockfile-metadata-file", + "--generate-lockfiles-sync", + ], + env_inherit=PYTHON_BOOTSTRAP_ENV, + ) + req = GenerateUvLockfile( + requirements=FrozenOrderedSet(["ansicolors==1.1.8"]), + find_links=FrozenOrderedSet([]), + interpreter_constraints=InterpreterConstraints(["CPython==3.14.*"]), + resolve_name="test", + lockfile_dest="test.lock", + diff=True, + ) + + rule_runner.write_digest(rule_runner.request(GenerateLockfileResult, [req]).digest) + rule_runner.write_files({"test.lock": "not valid toml [[["}) + + result = rule_runner.request(GenerateLockfileResult, [req]) + digest_contents = rule_runner.request(DigestContents, [result.digest]) + by_path = {fc.path: fc for fc in digest_contents} + lock_data = tomllib.loads(by_path["test.lock"].content.decode()) + packages = {pkg["name"]: pkg["version"] for pkg in lock_data.get("package", [])} + assert packages["ansicolors"] == "1.1.8" diff --git a/src/python/pants/backend/python/util_rules/lockfile_diff.py b/src/python/pants/backend/python/util_rules/lockfile_diff.py index 053098cea5e..c28c220ef58 100644 --- a/src/python/pants/backend/python/util_rules/lockfile_diff.py +++ b/src/python/pants/backend/python/util_rules/lockfile_diff.py @@ -143,9 +143,9 @@ async def _generate_lockfile_diff( old_content_entries = await get_digest_contents(loaded.lockfile_digest) old_content = next(iter(old_content_entries)).content old_packages = _parse_lockfile_packages(old_content, loaded.lockfile_format, path) - except EngineError: - # May fail if the file doesn't exist, which is expected the first time a new lockfile - # is generated. + except (EngineError, tomllib.TOMLDecodeError): + # May fail if the file doesn't exist or is malformed, which is expected the first time a + # new lockfile is generated or when the existing one needs to be overwritten. pass return LockfileDiff.create(