├── .github └── workflows │ └── deps.yml ├── LICENSE ├── README.md ├── bin ├── compile └── detect └── deps.yml /.github/workflows/deps.yml: -------------------------------------------------------------------------------- 1 | name: deps 2 | 3 | on: 4 | schedule: 5 | - cron: 0 0 * * Mon 6 | workflow_dispatch: {} 7 | 8 | jobs: 9 | 10 | deps: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - id: generate_token 14 | uses: tibdex/github-app-token@v1 15 | with: 16 | app_id: ${{ secrets.DEPS_GITHUB_APP_ID }} 17 | private_key: ${{ secrets.DEPS_GITHUB_APP_KEY }} 18 | - uses: actions/checkout@v2 19 | with: 20 | token: ${{ steps.generate_token.outputs.token }} 21 | - run: curl https://deps.app/install.sh | bash -s -- -b $HOME/bin 22 | - run: $HOME/bin/deps ci 23 | env: 24 | DEPS_TOKEN: ${{ secrets.DEPS_TOKEN }} 25 | DEPS_GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Dropseed, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!TIP] 2 | > [Official Heroku buildpacks now support uv](https://devcenter.heroku.com/changelog-items/3238) 3 | 4 | # heroku-buildpack-uv 5 | 6 | A Heroku Buildpack for [uv](https://github.com/astral-sh/uv). 7 | 8 | Generates `requirements.txt` and `.python-version` files from your `uv.lock` file. 9 | 10 | This buildpack is essentially a pre-processor for the [`heroku/python`](https://github.com/heroku/heroku-buildpack-python) buildpack, so it should be added *before* `heroku/python`. 11 | 12 | For example: 13 | 14 | ``` 15 | heroku buildpacks:clear 16 | heroku buildpacks:add https://github.com/dropseed/heroku-buildpack-uv.git 17 | heroku buildpacks:add heroku/python 18 | ``` 19 | 20 | Originally developed for use with [Plain](https://plainframework.com/), but can be used with any Python project using uv. 21 | -------------------------------------------------------------------------------- /bin/compile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | function log() { 6 | echo "-----> $*" 7 | } 8 | 9 | function indent() { 10 | sed -e 's/^/ /' 11 | } 12 | 13 | export BUILD_DIR="$1" 14 | export CACHE_DIR="$2" 15 | export ENV_DIR="$3" 16 | 17 | export UV_LINK_MODE=copy # Default to copy mode (shows a warning otherwise) 18 | export UV_CACHE_DIR="$CACHE_DIR" 19 | 20 | # Make sure the CACHE_DIR exists 21 | # https://devcenter.heroku.com/articles/buildpack-api#caching 22 | mkdir -p "$CACHE_DIR" 23 | 24 | shopt -s nullglob 25 | 26 | # Run the rest of the script from the build directory 27 | cd "$BUILD_DIR" 28 | 29 | # Set any UV_ environment variables 30 | # https://docs.astral.sh/uv/configuration/environment/ 31 | for env_file in "$ENV_DIR"/UV_* ; do 32 | export "$(basename "$env_file")=$(cat "$env_file" 2>/dev/null)" 33 | done 34 | 35 | if [ -z "${UV_VERSION:-}" ] ; then 36 | export UV_VERSION=0.6.17 37 | log "No uv version specified in UV_VERSION config var. Defaulting to $UV_VERSION." 38 | else 39 | log "Using uv version from UV_VERSION config var: $UV_VERSION" 40 | fi 41 | 42 | log "Install uv" 43 | curl -LsSf "https://astral.sh/uv/$UV_VERSION/install.sh" | sh 2>&1 | indent 44 | 45 | # Make the uv commands available 46 | source "$HOME/.local/bin/env" 47 | 48 | # Install a Python version with uv so we get more 49 | # recent releases by default 50 | log "Install uv Python version" 51 | uv python install 2>&1 | indent 52 | 53 | if [ -z "${UV_EXPORT_OPTIONS:-}" ] ; then 54 | log "Export requirements.txt from uv" 55 | uv export --output-file requirements.txt --no-dev --no-emit-project --frozen 2>&1 | indent 56 | 57 | log "Export requirements-test.txt from uv (for Heroku CI)" 58 | uv export --output-file requirements-test.txt --no-emit-project --frozen 2>&1 | indent 59 | else 60 | log "Export requirements.txt from uv (with $UV_EXPORT_OPTIONS)" 61 | echo "$UV_EXPORT_OPTIONS" | xargs uv export --output-file requirements.txt --no-dev --no-emit-project --frozen 2>&1 | indent 62 | 63 | log "Export requirements-test.txt from uv (for Heroku CI, with $UV_EXPORT_OPTIONS)" 64 | echo "$UV_EXPORT_OPTIONS" | xargs uv export --output-file requirements-test.txt --no-emit-project --frozen 2>&1 | indent 65 | fi 66 | 67 | if [ -f runtime.txt ] ; then 68 | log "runtime.txt found, skipping runtime.txt generation" >&2 69 | exit 0 70 | elif [ -f .python-version ]; then 71 | log ".python-version found, skipping .python-version generation" >&2 72 | exit 0 73 | else 74 | log "Generating .python-version from uv Python version" 75 | PYTHON_VERSION_OUTPUT=$(uv run python --version) 76 | echo "${PYTHON_VERSION_OUTPUT//Python /}" > .python-version 77 | cat .python-version | indent 78 | fi 79 | 80 | log "Uninstalling uv-managed Python versions" 81 | uv python uninstall --all 2>&1 | indent || echo "Failed to uninstall uv-managed Python versions..." 82 | -------------------------------------------------------------------------------- /bin/detect: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | BUILD_DIR="$1" 6 | 7 | if [ -f "$BUILD_DIR/uv.lock" ] ; then 8 | echo "uv" 9 | exit 0 10 | else 11 | exit 1 12 | fi 13 | -------------------------------------------------------------------------------- /deps.yml: -------------------------------------------------------------------------------- 1 | version: 3 2 | dependencies: 3 | - type: manual 4 | settings: 5 | deps: 6 | uv-version: 7 | collect: | 8 | grep 'UV_VERSION=' bin/compile | cut -d '=' -f 2 9 | act: | 10 | UV_LATEST=$(curl -s https://api.github.com/repos/astral-sh/uv/releases/latest | jq -r '.tag_name') 11 | sed -i "s/UV_VERSION=.*/UV_VERSION=$UV_LATEST/" bin/compile 12 | --------------------------------------------------------------------------------