From 9e22a0ee8763b298a5106a398291a0cc76ee1148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilson=20J=C3=BAnior?= Date: Tue, 16 Jun 2026 18:54:19 -0300 Subject: [PATCH] python: add uv virtualenv bin directory to PATH After `uv sync`, installed packages and console scripts live inside the virtualenv and are not accessible unless the venv bin dir is on PATH. This prepends the venv bin directory to both the user (~/.profile) and system (/etc/profile) profiles so that runtime processes can locate uv-installed executables. Respects UV_PROJECT_ENVIRONMENT when set. Co-Authored-By: Claude Opus 4.6 --- python/deploy | 15 ++++++++++ tests/python/tests.bats | 62 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/python/deploy b/python/deploy index dcaee41..f3b6f07 100755 --- a/python/deploy +++ b/python/deploy @@ -268,6 +268,21 @@ elif [ -f "${CURRENT_DIR}/uv.lock" ]; then # UV_INDEX_URL and UV_EXTRA_INDEX_URL are natively supported by uv uv sync --no-dev --no-install-project --python-preference only-system + + # uv installs dependencies into a virtualenv (default: .venv in the project + # dir, or $UV_PROJECT_ENVIRONMENT). Prepend its bin dir to PATH so the + # installed packages and console scripts are available at runtime. + VENV_PATH="${UV_PROJECT_ENVIRONMENT:-${CURRENT_DIR}/.venv}" + if [ -d "${VENV_PATH}/bin" ]; then + echo "Adding uv virtualenv to PATH: ${VENV_PATH}/bin" + # deploy runs as the (non-root) application user, so the user profile is + # written directly while the system profile needs sudo (see buildpack/deploy). + echo "export PATH=${VENV_PATH}/bin:\${PATH}" | tee -a ${HOME}/.profile >/dev/null + echo "export PATH=${VENV_PATH}/bin:\${PATH}" | sudo tee -a /etc/profile >/dev/null + else + echo "WARNING: uv virtualenv not found at ${VENV_PATH}, PATH not updated" + fi + elif [ -f "${CURRENT_DIR}/Pipfile.lock" ]; then echo "Pipfile.lock detected, using 'pipenv install --system --deploy' to install dependencies" diff --git a/tests/python/tests.bats b/tests/python/tests.bats index 68d28f0..f6d9324 100644 --- a/tests/python/tests.bats +++ b/tests/python/tests.bats @@ -497,6 +497,68 @@ EOF rm -rf ${CURRENT_DIR}/pyproject.toml ${CURRENT_DIR}/uv.lock ${CURRENT_DIR}/.venv } +@test "uv.lock adds virtualenv bin to PATH profile" { + unset PYTHON_VERSION + cp uv_pyproject.toml ${CURRENT_DIR}/pyproject.toml + cp uv.lock ${CURRENT_DIR}/ + + # Start from clean profile files so we can assert exactly what gets appended + # (/etc/profile is root-owned, deploy writes to it via sudo) + rm -f ${HOME}/.profile + sudo rm -f /etc/profile + + run /var/lib/tsuru/deploy + assert_success + + [[ "$output" == *"uv.lock detected"* ]] + [[ "$output" == *"Adding uv virtualenv to PATH: ${CURRENT_DIR}/.venv/bin"* ]] + + # The venv bin dir must be exported in both the user and system profiles + run grep -F "export PATH=${CURRENT_DIR}/.venv/bin:\${PATH}" ${HOME}/.profile + assert_success + run grep -F "export PATH=${CURRENT_DIR}/.venv/bin:\${PATH}" /etc/profile + assert_success + + # A fresh login shell that sources the profile must expose the venv binaries + run bash -lc 'command -v python' + assert_success + [[ "$output" == *"${CURRENT_DIR}/.venv/bin/python"* ]] + + rm -rf ${CURRENT_DIR}/pyproject.toml ${CURRENT_DIR}/uv.lock ${CURRENT_DIR}/.venv + rm -f ${HOME}/.profile + sudo rm -f /etc/profile +} + +@test "uv.lock respects UV_PROJECT_ENVIRONMENT for PATH profile" { + unset PYTHON_VERSION + export UV_PROJECT_ENVIRONMENT=${CURRENT_DIR}/custom-venv + cp uv_pyproject.toml ${CURRENT_DIR}/pyproject.toml + cp uv.lock ${CURRENT_DIR}/ + + rm -f ${HOME}/.profile + sudo rm -f /etc/profile + + run /var/lib/tsuru/deploy + assert_success + + [[ "$output" == *"uv.lock detected"* ]] + [[ "$output" == *"Adding uv virtualenv to PATH: ${CURRENT_DIR}/custom-venv/bin"* ]] + + run grep -F "export PATH=${CURRENT_DIR}/custom-venv/bin:\${PATH}" ${HOME}/.profile + assert_success + + pushd ${CURRENT_DIR} + run uv pip freeze --python custom-venv/bin/python + popd + assert_success + [[ "$output" == *"msgpack"* ]] + + rm -rf ${CURRENT_DIR}/pyproject.toml ${CURRENT_DIR}/uv.lock ${CURRENT_DIR}/custom-venv + rm -f ${HOME}/.profile + sudo rm -f /etc/profile + unset UV_PROJECT_ENVIRONMENT +} + @test "install from uv.lock with custom uv version" { unset PYTHON_VERSION export PYTHON_UV_VERSION=0.7.0