Skip to content

Commit 3a425d3

Browse files
committed
[build] use bazel to update python requirements
1 parent f40b8ba commit 3a425d3

File tree

4 files changed

+123
-81
lines changed

4 files changed

+123
-81
lines changed

Rakefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,14 @@ namespace :py do
563563
@git.add(conf)
564564
end
565565

566+
desc 'Update Python dependencies'
567+
task :update do
568+
Bazel.execute('run', [], '//scripts:update_py_deps')
569+
Bazel.execute('run', [], '//py:requirements.update')
570+
@git.add('py/requirements.txt')
571+
@git.add('py/requirements_lock.txt')
572+
end
573+
566574
namespace :test do
567575
desc 'Python unit tests'
568576
task :unit do

scripts/BUILD.bazel

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ py_binary(
4747
],
4848
)
4949

50+
py_binary(
51+
name = "update_py_deps",
52+
srcs = ["update_py_deps.py"],
53+
)
54+
5055
java_binary(
5156
name = "google-java-format",
5257
jvm_flags = [

scripts/update_py_dependencies.sh

Lines changed: 0 additions & 81 deletions
This file was deleted.

scripts/update_py_deps.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#!/usr/bin/env python
2+
3+
import json
4+
import subprocess
5+
import sys
6+
import tempfile
7+
from pathlib import Path
8+
9+
# Updates py/requirements.txt with latest compatible versions using pip
10+
# Run with: bazel run //scripts:update_py_deps
11+
12+
13+
def main():
14+
script_dir = Path(__file__).resolve().parent
15+
requirements_file = script_dir.parent / "py" / "requirements.txt"
16+
17+
if not requirements_file.exists():
18+
print(f"Error: {requirements_file} not found")
19+
return
20+
21+
print(f"Checking {requirements_file}")
22+
23+
# Parse current requirements preserving original format
24+
current_lines = requirements_file.read_text().strip().split("\n")
25+
packages = [] # (original_line, package_name_with_extras, package_name_normalized)
26+
for line in current_lines:
27+
line = line.strip()
28+
if line and not line.startswith("#") and "==" in line:
29+
name_with_extras, version = line.split("==", 1)
30+
# Normalize: remove extras for pip queries
31+
name_normalized = name_with_extras.split("[")[0].lower()
32+
packages.append((line, name_with_extras, name_normalized))
33+
34+
with tempfile.TemporaryDirectory() as tmpdir:
35+
venv_dir = Path(tmpdir) / "venv"
36+
37+
# Create virtual environment
38+
print("Creating temporary virtual environment...")
39+
subprocess.run(
40+
[sys.executable, "-m", "venv", str(venv_dir)],
41+
check=True,
42+
capture_output=True,
43+
)
44+
45+
pip = venv_dir / "bin" / "pip"
46+
47+
# Upgrade pip first
48+
subprocess.run(
49+
[str(pip), "install", "-q", "--upgrade", "pip"],
50+
check=True,
51+
capture_output=True,
52+
)
53+
54+
# Install packages (with extras) to let pip resolve versions
55+
install_names = [p[1] for p in packages] # name_with_extras
56+
print(f"Installing {len(install_names)} packages...")
57+
result = subprocess.run(
58+
[str(pip), "install", "-q"] + install_names,
59+
capture_output=True,
60+
text=True,
61+
)
62+
if result.returncode != 0:
63+
print(f"Error installing packages:\n{result.stderr}")
64+
return
65+
66+
# Get installed versions
67+
result = subprocess.run(
68+
[str(pip), "list", "--format=json"],
69+
capture_output=True,
70+
text=True,
71+
check=True,
72+
)
73+
installed = {pkg["name"].lower(): pkg["version"] for pkg in json.loads(result.stdout)}
74+
75+
# Update versions in original lines
76+
updated_lines = []
77+
updates = []
78+
for orig_line, name_with_extras, name_normalized in packages:
79+
old_version = orig_line.split("==")[1]
80+
new_version = installed.get(name_normalized)
81+
82+
if new_version and new_version != old_version:
83+
updates.append((name_with_extras, old_version, new_version))
84+
updated_lines.append(f"{name_with_extras}=={new_version}")
85+
print(f" {name_with_extras}: {old_version} -> {new_version}")
86+
else:
87+
updated_lines.append(orig_line)
88+
89+
if not updates:
90+
print("\nAll packages are up to date!")
91+
return
92+
93+
# Rebuild file preserving non-package lines
94+
new_content = []
95+
pkg_idx = 0
96+
for line in current_lines:
97+
stripped = line.strip()
98+
if stripped and not stripped.startswith("#") and "==" in stripped:
99+
new_content.append(updated_lines[pkg_idx])
100+
pkg_idx += 1
101+
else:
102+
new_content.append(line)
103+
104+
requirements_file.write_text("\n".join(new_content) + "\n")
105+
print(f"\nUpdated {len(updates)} package(s)")
106+
print("\nNow run: bazel run //py:requirements.update")
107+
108+
109+
if __name__ == "__main__":
110+
main()

0 commit comments

Comments
 (0)