Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/uv-lock-automation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ on:
required: false
default: "" # Run for all dirs specified in docker/scripts/uv-lock-gen/uv-lock.sh

# Serialize per branch. Pushing twice in quick succession used to start
# overlapping runs that raced each other's auto-commit, and the loser failed the
# whole job with "failed to push some refs". Only the newest run's lockfiles are
# of any interest, so cancel whatever is already in flight for the same branch.
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: true

jobs:
update_uv_lock:
name: Update UV lock in all directories
Expand Down
93 changes: 22 additions & 71 deletions docker/scripts/uv-lock-gen/uv-lock.sh
Original file line number Diff line number Diff line change
@@ -1,85 +1,39 @@
#!/bin/bash
set -o pipefail

# Extract local path dependencies from [tool.uv.sources] in a pyproject.toml.
# Returns resolved pyproject.toml paths for each local dependency.
get_local_dep_pyprojects() {
local dir="$1"
local file_path="$dir/pyproject.toml"

grep -A1 'path\s*=' "$file_path" 2>/dev/null \
| grep -oP 'path\s*=\s*"\K[^"]+' \
| while read -r rel_path; do
# Resolve relative to the service directory
local dep_pyproject
if [[ "$dir" == "." ]]; then
dep_pyproject="$rel_path/pyproject.toml"
else
dep_pyproject="$dir/$rel_path/pyproject.toml"
fi
# Normalize the path
dep_pyproject=$(realpath --relative-to=. "$dep_pyproject" 2>/dev/null || echo "$dep_pyproject")
if [[ -f "$dep_pyproject" ]]; then
echo "$dep_pyproject"
fi
done
}

# Check if a directory's own pyproject.toml or any of its local
# path dependencies' pyproject.toml files have changed vs origin/main.
has_dependency_changes() {
local dir="$1"
local file_path="$dir/pyproject.toml"

# Check direct changes
if ! git diff --quiet origin/main -- "$file_path"; then
echo "[$dir] Changes detected in '$file_path'"
return 0
fi

# Check transitive local dependency changes
local dep_pyprojects
dep_pyprojects=$(get_local_dep_pyprojects "$dir")
for dep in $dep_pyprojects; do
if ! git diff --quiet origin/main -- "$dep"; then
echo "[$dir] Changes detected in transitive dependency '$dep'"
return 0
fi
done

return 1
}

# Function to update the lockfile in a directory
#
# `uv lock --check` is the authoritative staleness test: it exits non-zero only
# when uv.lock is missing or no longer agrees with pyproject.toml, transitive
# local path dependencies included. Gating on it means a PR that already carries
# correct lockfiles is a genuine no-op, rather than a re-resolve that rewrites
# the tree and leaves the auto-commit step with nothing useful to push.
#
# `uv lock` (not `uv sync`) is what we want here: the workflow only ever commits
# uv.lock, so building a virtualenv and installing every package is wasted work
# and the single largest source of failures in this job.
update_lockfile() {
dir="$1"
file_path="$dir/pyproject.toml"
local dir="$1"
local file_path="$dir/pyproject.toml"

if [[ ! -f "$file_path" ]]; then
echo "[$dir] No pyproject.toml found in '$dir'"
return 0
fi

echo "[$dir] Checking '$file_path' and its dependencies for changes against origin/main..."
if has_dependency_changes "$dir"; then
echo "[$dir] Updating uv.lock for '$dir' ..."
# Run in a subshell so the directory change is scoped to this invocation
(
cd "$dir" || exit 1

# Move to the directory if it's not root
if [[ "$dir" != "." ]]; then
cd "$dir" || return 1
echo "[$dir] Checking whether uv.lock is up to date..."
if uv lock --check >/dev/null 2>&1; then
echo "[$dir] uv.lock is already up to date, nothing to do"
exit 0
fi

# Use uv to generate lock file
echo "[$dir] Updating uv.lock..."
uv sync 2>&1 | sed "s|^|[$dir] |" || return 1

# Go back to root if moved to a subdirectory
if [[ "$dir" != "." ]]; then
cd - || return 1
fi
else
echo "[$dir] No changes detected in '$file_path' or its dependencies"
fi
echo "[$dir] uv.lock is out of date, regenerating..."
uv lock 2>&1 | sed "s|^|[$dir] |"
)
}

# https://unix.stackexchange.com/a/124148
Expand Down Expand Up @@ -115,9 +69,6 @@ if [ "$#" -gt 0 ]; then
directories=("$@")
fi

# To compare against main branch
git fetch origin main

# Array to store the job PIDs and directories
pids=()
dirs=()
Expand Down
Loading