-
Notifications
You must be signed in to change notification settings - Fork 510
Expand file tree
/
Copy pathuv_utils.py
More file actions
111 lines (94 loc) · 3.5 KB
/
uv_utils.py
File metadata and controls
111 lines (94 loc) · 3.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Helper utilities for working with uv in installation scripts."""
import os
import shutil
import subprocess
import sys
def get_uv_command():
"""
Returns the command to run uv, either as a binary in PATH or as a module.
Attempts to install uv via pip if not found.
"""
# 1. Try finding 'uv' in PATH
uv_binary = shutil.which("uv")
if uv_binary:
return [uv_binary]
# 2. Try running it as a module
try:
subprocess.run([sys.executable, "-m", "uv", "--version"], check=True, capture_output=True)
return [sys.executable, "-m", "uv"]
except (subprocess.CalledProcessError, FileNotFoundError):
pass
# 3. Fall back to installing via pip
try:
print("uv not found in PATH or as a module. Attempting to install it via pip...")
subprocess.run([sys.executable, "-m", "pip", "install", "uv"], check=True, capture_output=True)
# Check PATH again after installation
uv_binary = shutil.which("uv")
if uv_binary:
return [uv_binary]
return [sys.executable, "-m", "uv"]
except subprocess.CalledProcessError as e:
print(f"Error installing uv via pip: {e}")
print(f"Stderr: {e.stderr.decode()}")
sys.exit(1)
def run_install(requirements_files=None, paths=None, editable_paths=None):
"""
Executes the appropriate uv install command (uv add or uv pip install).
Args:
requirements_files: List of paths to requirements.txt files.
paths: List of paths to local packages or directories (non-editable).
editable_paths: List of paths to local packages or directories (editable).
"""
uv_command = get_uv_command()
is_uv_project = os.path.exists("uv.lock")
# We run installations in two steps if we have both standard and editable items,
# because 'uv add --editable' cannot be mixed with non-local requirements.
# Step 1: Standard installations
if requirements_files or paths:
if is_uv_project:
cmd = uv_command + ["add", "--frozen"]
else:
cmd = uv_command + ["pip", "install", "--no-deps"]
if requirements_files:
for req in requirements_files:
cmd.extend(["-r", str(req)])
if paths:
cmd.extend(paths)
_execute_command(cmd)
# Step 2: Editable installations
if editable_paths:
if is_uv_project:
cmd = uv_command + ["add", "--frozen", "--editable"]
else:
cmd = uv_command + ["pip", "install", "--no-deps", "-e"]
cmd.extend(editable_paths)
_execute_command(cmd)
def _execute_command(cmd):
"""Helper to execute a command with logging and error handling."""
try:
print(f"Executing: {' '.join(cmd)}")
subprocess.run(cmd, check=True, capture_output=True, text=True)
print("Success!")
except subprocess.CalledProcessError as e:
print(f"Command failed with exit status {e.returncode}.")
print("--- Stderr ---")
print(e.stderr)
print("--- Stdout ---")
print(e.stdout)
sys.exit(e.returncode)
except (OSError, FileNotFoundError) as e:
print(f"An OS-level error occurred: {e}")
sys.exit(1)