Repository navigation
Feature - add uv support - #1260
Open
dominikmuellerdmde wants to merge 14 commits into
Open
dominikmuellerdmde wants to merge 14 commits into
dominikmuellerdmde wants to merge 14 commits into
Conversation
When an app provides a uv.lock but no requirements.txt, install uv and run 'uv export --frozen' to generate a temporary requirements.txt, just like it is done for Pipfile.lock. The regular pip flow then installs the dependencies, so vendoring, libffi/libmemcache detection and stale package removal keep working for uv apps. Only the default (non-dev) dependencies are exported. The project itself is emitted only when it is a package and is then installed non-editable.
uv projects usually pin their interpreter in .python-version instead of runtime.txt. For apps with a uv.lock, translate the first CPython version in that file into runtime.txt. An explicit runtime.txt still wins, and apps without a uv.lock keep ignoring .python-version.
'uv export' does not emit the indexes configured via [[tool.uv.index]], so pip would silently fall back to PyPI for apps that use a private package index. Read the registry URLs from uv.lock and prepend them as -i/--extra-index-url options, as is already done for Pipfile.lock sources. PyPI stays the primary index when it is used. Indexes configured via PIP_INDEX_URL or PIP_EXTRA_INDEX_URL take precedence, as uv strips credentials from the URLs in uv.lock.
Add a uv-managed Flask fixture that pins its Python version via .python-version and has a dev dependency group, and verify that it deploys with the dependencies exported from uv.lock. Also verify that an existing requirements.txt takes precedence over uv.lock.
Package names are normalized as defined in PEP 503 before comparing them. Generated requirements files, such as those exported by uv, use lowercase names, so packages like 'PyNaCl' or 'pyOpenSSL' were not detected and libffi was not installed for them.
For apps using Pipfile.lock or uv.lock, supply generates a temporary requirements.txt and deletes it at the end of the supply phase. Finalize then no longer found Django in it and silently skipped collectstatic. Keep a copy of the generated requirements.txt in the deps dir and let finalize check it as well.
Install the packages of uv apps with 'uv sync --frozen' directly into the Python installation of the buildpack instead of exporting them for pip. uv verifies every package against the hashes in uv.lock and supports all of uv's features, like workspaces, [tool.uv.sources] and index configuration including credentials via UV_INDEX_* variables. Reading the package indexes from uv.lock for pip is no longer needed. uv.lock is still exported to a temporary requirements.txt, now with hashes, so that packages which need libffi, libmemcached or collectstatic are detected as before. uv uses the system certificate store like pip does, and its cache is kept in the app cache. Vendored uv apps are still installed with pip.
'uv sync' only downloads the files referenced in uv.lock and cannot install from a vendor directory. For vendored uv apps, install the packages exported from uv.lock with 'uv pip install --offline --no-index --find-links vendor' instead. The exported requirements contain the hashes from uv.lock, so modified or mismatching files in the vendor directory are rejected.
Verify that uv apps are installed with uv sync, that staging fails when a package does not match the hash in uv.lock, and that collectstatic runs for a Django app managed with uv.
The buildpack manifest now provides uv as a pip wheel built by the buildpacks pipeline (for cflinuxfs4 and cflinuxfs5) instead of the upstream release binary. Install that wheel with pip into /tmp/uv, so uv stays out of the droplet, and run uv from /tmp/uv/bin/uv.
- Ignore .python-version when the app provides a requirements.txt, as uv.lock is ignored in that case as well. - Only keep a copy of the generated requirements.txt in the deps dir for uv apps. Pipfile apps keep their previous behaviour, so collectstatic does not suddenly run for Pipfile Django apps. - Only detect uv apps that provide both uv.lock and pyproject.toml. - Start from an empty /tmp/uv when installing uv. - Mention the build backend when a vendored uv install fails. - Compile the package name regex once.
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.
Warning
This PR was written largely with AI assistance (Claude Code). That includes the code, the tests and this description. I have reviewed the changes and tested them on our own Cloud Foundry foundation, but please review and test it as carefully as any change you didn't write yourself. Don't assume the AI got it right.
A short explanation of the proposed change:
This adds support for apps managed with uv (
pyproject.toml+uv.lock):bin/detectrecognises apps that contain both auv.lockand apyproject.toml.manifest.ymlis installed with pip into/tmp/uv, so uv stays out of the droplet..python-versionunless aruntime.txtexists. uv never downloads its own Python.uv sync --frozen --no-dev --no-editable. Every package is checked against the hashes inuv.lock, and staging fails on a mismatch. Indexes configured via[[tool.uv.index]]are used, with credentials passed asUV_INDEX_<NAME>_*environment variables.vendor/withuv pip install --offline, also checked against the hashes inuv.lock.requirements.txtexported fromuv.lockis used for the existing package-specific steps (libffi, libmemcached, Djangocollectstatic). Package names in requirements are now matched case-insensitively and with-,_and.treated as equal, as PEP 503 specifies.requirements.txt, it takes precedence:uv.lockand.python-versionare ignored and pip is used as before. Pipfile apps are unchanged.Why the existing uv support is not sufficient:
manifest.ymlalready contains auvdependency (since 0.12.23, now 0.13.5 for cflinuxfs4 and cflinuxfs5), but nothing in the buildpack uses it. The commits that added it only touchmanifest.yml. As a result, uv apps don't work today:bin/detectonly looks forrequirements.txt,setup.py,environment.ymlandPipfile, so an app with justpyproject.tomlanduv.lockis not detected as a Python app.-b, Python and pip are installed but the dependency step is skipped because there is norequirements.txt. Staging succeeds and the app crashes at start withModuleNotFoundError..tgz, not the standaloneuvbinary. It has to be installed with pip before it can run, which this PR does.An explanation of the use cases your change solves:
uv is becoming one of the standard tools for Python projects. Today, teams using it have to export a
requirements.txtbefore every push (uv export) and keep it in sync withuv.lock, which is easy to forget and loses the hash checking from the lock file. With this change, a uv project can be pushed as-is:uv python pinis respected;collectstaticas with pip.Testing:
Unit tests for supply, finalize and requirements.
New integration tests in
src/python/integration/uv_test.go:uv.lockwith a wrong hash, which must fail staging;collectstaticruns;uv.lockandrequirements.txt, checking that pip is used.The full integration suite was run against a local CF on cflinuxfs4, cached and uncached. The uv tests and all other suites pass. The only failures were the AppDynamics, Dynatrace and Proxy tests, which also fail with an unmodified
masterbuild on that foundation.I have viewed signed and have submitted the Contributor License Agreement
I have made this pull request to the master branch
I have added an integration test