blob: 552dc2b56a8850ddcc8839ed6c441bf2e874b6f9 [file] [log] [blame]
#!/usr/bin/env python
import json
import subprocess
import sys
import tempfile
from pathlib import Path
# Updates py/requirements.txt with latest compatible versions using pip
# Run with: bazel run //scripts:update_py_deps
def main():
script_dir = Path(__file__).resolve().parent
requirements_file = script_dir.parent / "py" / "requirements.txt"
if not requirements_file.exists():
raise FileNotFoundError(f"{requirements_file} not found")
print(f"Checking {requirements_file}")
# Parse current requirements preserving original format
current_lines = requirements_file.read_text().strip().split("\n")
packages = [] # (original_line, package_name_with_extras, package_name_normalized)
for line in current_lines:
line = line.strip()
if line and not line.startswith("#") and "==" in line:
name_with_extras, version = line.split("==", 1)
# Normalize: remove extras for pip queries
name_normalized = name_with_extras.split("[")[0].lower()
packages.append((line, name_with_extras, name_normalized))
with tempfile.TemporaryDirectory() as tmpdir:
venv_dir = Path(tmpdir) / "venv"
# Create virtual environment
print("Creating temporary virtual environment...")
subprocess.run(
[sys.executable, "-m", "venv", str(venv_dir)],
check=True,
capture_output=True,
)
pip = venv_dir / "bin" / "pip"
# Upgrade pip first
subprocess.run(
[str(pip), "install", "-q", "--upgrade", "pip"],
check=True,
capture_output=True,
)
# Install packages (with extras) to let pip resolve versions
install_names = [p[1] for p in packages] # name_with_extras
print(f"Installing {len(install_names)} packages...")
result = subprocess.run(
[str(pip), "install", "-q"] + install_names,
capture_output=True,
text=True,
)
if result.returncode != 0:
raise RuntimeError(f"Error installing packages:\n{result.stderr}")
# Get installed versions
result = subprocess.run(
[str(pip), "list", "--format=json"],
capture_output=True,
text=True,
check=True,
)
installed = {pkg["name"].lower(): pkg["version"] for pkg in json.loads(result.stdout)}
# Update versions in original lines
updated_lines = []
updates = []
for orig_line, name_with_extras, name_normalized in packages:
old_version = orig_line.split("==")[1]
new_version = installed.get(name_normalized)
if new_version and new_version != old_version:
updates.append((name_with_extras, old_version, new_version))
updated_lines.append(f"{name_with_extras}=={new_version}")
print(f" {name_with_extras}: {old_version} -> {new_version}")
else:
updated_lines.append(orig_line)
if not updates:
print("\nAll packages are up to date!")
return
# Rebuild file preserving non-package lines
new_content = []
pkg_idx = 0
for line in current_lines:
stripped = line.strip()
if stripped and not stripped.startswith("#") and "==" in stripped:
new_content.append(updated_lines[pkg_idx])
pkg_idx += 1
else:
new_content.append(line)
requirements_file.write_text("\n".join(new_content) + "\n")
print(f"\nUpdated {len(updates)} package(s)")
print("\nNow run: bazel run //py:requirements.update")
if __name__ == "__main__":
main()