Generate and use requirements lock files

This commit is contained in:
Robert Resch
2026-06-08 10:37:19 +00:00
parent 4f4aeff2b4
commit 32a9e115da
4 changed files with 28629 additions and 8 deletions
+4 -8
View File
@@ -104,15 +104,12 @@ jobs:
- name: Generate partial Python venv restore key
id: generate_python_cache_key
env:
HASH_REQUIREMENTS_TEST: ${{ hashFiles('requirements_test.txt', 'requirements_test_pre_commit.txt') }}
HASH_REQUIREMENTS: ${{ hashFiles('requirements.txt') }}
HASH_REQUIREMENTS_ALL: ${{ hashFiles('requirements_all.txt') }}
HASH_PACKAGE_CONSTRAINTS: ${{ hashFiles('homeassistant/package_constraints.txt') }}
HASH_REQUIREMENTS_CI_LOCK: ${{ hashFiles('requirements_ci.lock') }}
HASH_GEN_REQUIREMENTS: ${{ hashFiles('script/gen_requirements_all.py') }}
run: |
# Include HA_SHORT_VERSION to force the immediate creation
# of a new uv cache entry after a version bump.
echo "key=venv-${CACHE_VERSION}-${HA_SHORT_VERSION}-${HASH_REQUIREMENTS_TEST}-${HASH_REQUIREMENTS}-${HASH_REQUIREMENTS_ALL}-${HASH_PACKAGE_CONSTRAINTS}-${HASH_GEN_REQUIREMENTS}" >> $GITHUB_OUTPUT
echo "key=venv-${CACHE_VERSION}-${HA_SHORT_VERSION}-${HASH_REQUIREMENTS_CI_LOCK}-${HASH_GEN_REQUIREMENTS}" >> $GITHUB_OUTPUT
- name: Filter for core changes
uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
id: core
@@ -365,7 +362,7 @@ jobs:
RUNNER_OS: ${{ runner.os }}
RUNNER_ARCH: ${{ runner.arch }}
PYTHON_VERSION: ${{ steps.python.outputs.python-version }}
HASH_FILES: ${{ hashFiles('requirements.txt', 'requirements_all.txt', 'requirements_test.txt', 'homeassistant/package_constraints.txt') }}
HASH_FILES: ${{ hashFiles('requirements_ci.lock') }}
run: |
partial_key="${RUNNER_OS}-${RUNNER_ARCH}-${PYTHON_VERSION}-uv-"
echo "partial_key=${partial_key}" >> $GITHUB_OUTPUT
@@ -414,8 +411,7 @@ jobs:
python -m venv venv
. venv/bin/activate
python --version
uv pip install -r requirements.txt
uv pip install -r requirements_all.txt -r requirements_test.txt
uv pip install -r requirements_ci.lock --require-hashes
uv pip install -e . --config-settings editable_mode=compat
- name: Dump pip freeze
run: |
+14051
View File
File diff suppressed because it is too large Load Diff
+14519
View File
File diff suppressed because it is too large Load Diff
+55
View File
@@ -7,7 +7,9 @@ from operator import itemgetter
from pathlib import Path
import pkgutil
import re
import subprocess
import sys
import tempfile
import tomllib
from typing import Any
@@ -261,6 +263,11 @@ IGNORE_PRE_COMMIT_HOOK_ID = (
PACKAGE_REGEX = re.compile(r"^(?:--.+\s)?([-_\.\w\d]+).*==.+$")
PIP_COMPILE_LOCK_FILES = {
"requirements_all.lock": ("requirements_all.txt",),
"requirements_ci.lock": ("requirements_all.txt", "requirements_test.txt"),
}
def explore_module(package: str, explore_children: bool) -> list[str]:
"""Explore the modules."""
@@ -554,6 +561,28 @@ def diff_file(filename: str, content: str) -> list[str]:
)
def generate_lock_file(output_path: str, source_file: tuple[str, ...]) -> None:
"""Resolve requirements_all.txt into a hash-pinned lock file.
This shells out to `uv pip compile --generate-hashes`, which performs a
full networked dependency resolution, so callers should only invoke it
when requirements/dependencies have changed.
"""
subprocess.run(
[
"uv",
"pip",
"compile",
"--generate-hashes",
"--quiet",
"--output-file",
output_path,
*source_file,
],
check=True,
)
def main(validate: bool, ci: bool) -> int:
"""Run the script."""
if not Path("requirements_all.txt").is_file():
@@ -596,6 +625,16 @@ def main(validate: bool, ci: bool) -> int:
if diff:
errors.append("".join(diff))
with tempfile.TemporaryDirectory() as tmp_dir:
for lock_file, source_files in PIP_COMPILE_LOCK_FILES.items():
tmp_lock = str(Path(tmp_dir) / lock_file)
generate_lock_file(tmp_lock, source_files)
lock_diff = diff_file(
lock_file, Path(tmp_lock).read_text(encoding="utf-8")
)
if lock_diff:
errors.append("".join(lock_diff))
if errors:
print("ERROR - FOUND THE FOLLOWING DIFFERENCES")
print()
@@ -607,9 +646,25 @@ def main(validate: bool, ci: bool) -> int:
return 0
sourcefile_to_lockfile = {}
for lock_file, source_files in PIP_COMPILE_LOCK_FILES.items():
for source_file in source_files:
sourcefile_to_lockfile.setdefault(source_file, []).append(lock_file)
print(sourcefile_to_lockfile)
generate_lock_files = set()
for filename, content in files:
if lock_files := sourcefile_to_lockfile.get(filename):
file = Path(filename)
if not file.is_file() or file.read_text(encoding="utf-8") != content:
generate_lock_files.update(lock_files)
Path(filename).write_text(content)
for lock_file in generate_lock_files:
source_files = PIP_COMPILE_LOCK_FILES[lock_file]
generate_lock_file(lock_file, source_files)
return 0