Description
The following dockerfile is the minimum reproducible example of a situation I met in a real project CI.
The Dockerfile
runs with python 3.10 but there is a setuptools
dependency for python 3.12+ in pyproject.toml
.
The other dependency in pyproject.toml
is pycountry, on its 22.1.10 version, and it has a loose dependency to setuptools.
In this situation I would expect setuptools
to not be a direct dependency. However uv --resolution=lowest-direct
appears to resolve at the minimum setuptools available, that is 0.7.2. Of course, this is an old incompatible version, and the installation fails.
Removing the python_version>='3.12'
constraint on the setuptools dependency appears to solve the issue, so I suppose this is a cause of the bug.
pyproject.toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "foobar"
version = "0.0.1"
requires-python = ">=3.10"
dependencies = [
"pycountry >= 22.1.10",
"setuptools >= 50.0.0; python_version>='3.12'"
]
Dockerfile
FROM ghcr.io/astral-sh/uv:0.4-python3.10-bookworm-slim
WORKDIR /foobar
COPY foobar pyproject.toml /foobar/
RUN uv sync --resolution=lowest-direct
docker build .
[+] Building 11.3s (8/8) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.1s
=> => transferring dockerfile: 185B 0.0s
=> [internal] load metadata for ghcr.io/astral-sh/uv:0.4-python3.10-bookworm-slim 0.0s
=> [internal] load .dockerignore 0.1s
=> => transferring context: 2B 0.0s
=> [1/4] FROM ghcr.io/astral-sh/uv:0.4-python3.10-bookworm-slim 0.0s
=> [internal] load build context 0.1s
=> => transferring context: 326B 0.0s
=> CACHED [2/4] WORKDIR /foobar 0.0s
=> [3/4] COPY foobar pyproject.toml /foobar/ 0.3s
=> ERROR [4/4] RUN uv sync --resolution=lowest-direct 10.5s
------
> [4/4] RUN uv sync --resolution=lowest-direct:
1.423 Using CPython 3.10.15 interpreter at: /usr/local/bin/python
1.423 Creating virtual environment at: .venv
1.425 warning: No `requires-python` value found in the workspace. Defaulting to `>=3.10`.
8.149 Resolved 4 packages in 6.72s
9.249 error: Failed to prepare distributions
9.249 Caused by: Failed to download and build `setuptools==0.7.2`
9.249 Caused by: Build backend failed to determine requirements with `build_wheel()` (exit status: 1)
9.249
9.249 [stdout]
9.249 --- build/src/tests/api_tests.txt (original)
9.249 +++ build/src/tests/api_tests.txt (refactored)
9.249 @@ -39,7 +39,7 @@
9.249 >>> dist.py_version == sys.version[:3]
9.249 True
9.249
9.249 - >>> print dist.platform
9.249 + >>> print(dist.platform)
9.249 None
9.249
9.249 Including various computed attributes::
9.249 @@ -199,7 +199,7 @@
9.249 You can ask a WorkingSet to ``find()`` a distribution matching a requirement::
9.249
9.249 >>> from pkg_resources import Requirement
9.249 - >>> print ws.find(Requirement.parse("Foo==1.0")) # no match, return None
9.249 + >>> print(ws.find(Requirement.parse("Foo==1.0"))) # no match, return None
9.249 None
9.249
9.249 >>> ws.find(Requirement.parse("Bar==0.9")) # match, return distribution
9.249 @@ -212,7 +212,7 @@
9.249 ... ws.find(Requirement.parse("Bar==1.0"))
9.249 ... except pkg_resources.VersionConflict:
9.249 ... exc = sys.exc_info()[1]
9.249 - ... print(str(exc))
9.249 + ... print((str(exc)))
9.249 ... else:
9.249 ... raise AssertionError("VersionConflict was not raised")
9.249 (Bar 0.9 (http://example.com/something), Requirement.parse('Bar==1.0'))
9.249 @@ -222,7 +222,7 @@
9.249 once for each existing distribution in the working set, and then is called
9.249 again for new distributions added thereafter::
9.249
9.249 - >>> def added(dist): print "Added", dist
9.249 + >>> def added(dist): print("Added", dist)
9.249 >>> ws.subscribe(added)
9.249 Added Bar 0.9
9.249 >>> foo12 = Distribution(project_name="Foo", version="1.2", location="f12")
9.249 @@ -338,52 +338,52 @@
9.249 >>> from pkg_resources import invalid_marker as im, evaluate_marker as em
9.249 >>> import os
9.249
9.249 - >>> print(im("sys_platform"))
9.249 + >>> print((im("sys_platform")))
9.249 Comparison or logical expression expected
9.249
9.249 - >>> print(im("sys_platform==")) # doctest: +ELLIPSIS
9.249 + >>> print((im("sys_platform=="))) # doctest: +ELLIPSIS
9.249 unexpected EOF while parsing (...line 1)
9.249
9.249 - >>> print(im("sys_platform=='win32'"))
9.249 - False
9.249 -
9.249 - >>> print(im("sys=='x'"))
9.249 + >>> print((im("sys_platform=='win32'")))
9.249 + False
9.249 +
9.249 + >>> print((im("sys=='x'")))
9.249 Unknown name 'sys'
9.249
9.249 - >>> print(im("(extra)"))
9.249 + >>> print((im("(extra)")))
9.249 Comparison or logical expression expected
9.249
9.249 - >>> print(im("(extra")) # doctest: +ELLIPSIS
9.249 + >>> print((im("(extra"))) # doctest: +ELLIPSIS
9.249 unexpected EOF while parsing (...line 1)
9.249
9.249 - >>> print(im("os.open('foo')=='y'"))
9.249 + >>> print((im("os.open('foo')=='y'")))
9.249 Language feature not supported in environment markers
9.249
9.249 - >>> print(im("'x'=='y' and os.open('foo')=='y'")) # no short-circuit!
9.249 + >>> print((im("'x'=='y' and os.open('foo')=='y'"))) # no short-circuit!
9.249 Language feature not supported in environment markers
9.249
9.249 - >>> print(im("'x'=='x' or os.open('foo')=='y'")) # no short-circuit!
9.249 + >>> print((im("'x'=='x' or os.open('foo')=='y'"))) # no short-circuit!
9.249 Language feature not supported in environment markers
9.249
9.249 - >>> print(im("'x' < 'y'"))
9.249 + >>> print((im("'x' < 'y'")))
9.249 '<' operator not allowed in environment markers
9.249
9.249 - >>> print(im("'x' < 'y' < 'z'"))
9.249 + >>> print((im("'x' < 'y' < 'z'")))
9.249 Chained comparison not allowed in environment markers
9.249
9.249 - >>> print(im("r'x'=='x'"))
9.249 + >>> print((im("r'x'=='x'")))
9.249 Only plain strings allowed in environment markers
9.249
9.249 - >>> print(im("'''x'''=='x'"))
9.249 + >>> print((im("'''x'''=='x'")))
9.249 Only plain strings allowed in environment markers
9.249
9.249 - >>> print(im('"""x"""=="x"'))
9.249 + >>> print((im('"""x"""=="x"')))
9.249 Only plain strings allowed in environment markers
9.249
9.249 - >>> print(im(r"'x\n'=='x'"))
9.249 + >>> print((im(r"'x\n'=='x'")))
9.249 Only plain strings allowed in environment markers
9.249
9.249 - >>> print(im("os.open=='y'"))
9.249 + >>> print((im("os.open=='y'")))
9.249 Language feature not supported in environment markers
9.249
9.249 >>> em('"x"=="x"')
9.249
9.249 [stderr]
9.249 RefactoringTool: Skipping optional fixer: buffer
9.249 RefactoringTool: Skipping optional fixer: idioms
9.249 RefactoringTool: Skipping optional fixer: set_literal
9.249 RefactoringTool: Skipping optional fixer: ws_comma
9.249 RefactoringTool: Refactored build/src/tests/api_tests.txt
9.249 RefactoringTool: Files that were modified:
9.249 RefactoringTool: build/src/tests/api_tests.txt
9.249 root: copying setuptools/tests/indexes/test_links_priority/external.html -> build/src/setuptools/tests/indexes/test_links_priority
9.249 root: copying setuptools/tests/indexes/test_links_priority/simple/foobar/index.html -> build/src/setuptools/tests/indexes/test_links_priority/simple/foobar
9.249 root: copying docs/conf.py -> build/src/docs
9.249 root: copying docs/formats.txt -> build/src/docs
9.249 root: copying docs/index.txt -> build/src/docs
9.249 root: copying docs/easy_install.txt -> build/src/docs
9.249 root: copying docs/python3.txt -> build/src/docs
9.249 root: copying docs/using.txt -> build/src/docs
9.249 root: copying docs/setuptools.txt -> build/src/docs
9.249 root: copying docs/merge.txt -> build/src/docs
9.249 root: copying docs/roadmap.txt -> build/src/docs
9.249 root: copying docs/pkg_resources.txt -> build/src/docs
9.249 root: copying docs/merge-faq.txt -> build/src/docs
9.249 root: copying docs/_theme/nature/theme.conf -> build/src/docs/_theme/nature
9.249 root: copying docs/_theme/nature/static/pygments.css -> build/src/docs/_theme/nature/static
9.249 root: copying docs/_theme/nature/static/nature.css_t -> build/src/docs/_theme/nature/static
9.249 root: copying docs/Makefile -> build/src/docs
9.249 root: copying docs/_templates/indexsidebar.html -> build/src/docs/_templates
9.249 root: copying _markerlib/markers.py -> build/src/_markerlib
9.249 root: copying _markerlib/__init__.py -> build/src/_markerlib
9.249 root: copying ez_setup.py -> build/src
9.249 root: copying release.py -> build/src
9.249 root: copying setup.py -> build/src
9.249 root: copying distribute_setup.py -> build/src
9.249 root: copying pkg_resources.py -> build/src
9.249 root: copying easy_install.py -> build/src
9.249 root: copying CHANGES.txt -> build/src
9.249 root: copying CONTRIBUTORS.txt -> build/src
9.249 root: copying DEVGUIDE.txt -> build/src
9.249 root: copying README.txt -> build/src
9.249 root: copying MANIFEST.in -> build/src
9.249 root: copying launcher.c -> build/src
9.249 Traceback (most recent call last):
9.249 File "<string>", line 14, in <module>
9.249 File "/root/.cache/uv/builds-v0/.tmpiFWFBR/lib/python3.10/site-packages/setuptools/build_meta.py", line 333, in get_requires_for_build_wheel
9.249 return self._get_build_requires(config_settings, requirements=[])
9.249 File "/root/.cache/uv/builds-v0/.tmpiFWFBR/lib/python3.10/site-packages/setuptools/build_meta.py", line 303, in _get_build_requires
9.249 self.run_setup()
9.249 File "/root/.cache/uv/builds-v0/.tmpiFWFBR/lib/python3.10/site-packages/setuptools/build_meta.py", line 521, in run_setup
9.249 super().run_setup(setup_script=setup_script)
9.249 File "/root/.cache/uv/builds-v0/.tmpiFWFBR/lib/python3.10/site-packages/setuptools/build_meta.py", line 319, in run_setup
9.249 exec(code, locals())
9.249 File "<string>", line 34, in <module>
9.249 AttributeError: module 'distutils.util' has no attribute 'run_2to3'
------
Dockerfile:4
--------------------
2 | WORKDIR /foobar
3 | COPY foobar pyproject.toml /foobar/
4 | >>> RUN uv sync --resolution=lowest-direct
5 |
--------------------
ERROR: failed to solve: process "/bin/sh -c uv sync --resolution=lowest-direct" did not complete successfully: exit code: 2