@@ -27,7 +27,7 @@ def _find_uv_executable(self) -> str:
27
27
"""Find UV executable in PATH."""
28
28
uv_path = self ._osutils .which ("uv" )
29
29
if not uv_path :
30
- raise MissingUvError ()
30
+ raise MissingUvError (uv_path = "not found in PATH" )
31
31
return uv_path
32
32
33
33
@property
@@ -76,14 +76,19 @@ def uv_version(self) -> Optional[str]:
76
76
"""Get UV version."""
77
77
return get_uv_version (self ._uv .uv_executable , self ._osutils )
78
78
79
+ def _ensure_cache_dir (self , config : UvConfig , scratch_dir : str ) -> None :
80
+ """Ensure UV cache directory is configured."""
81
+ if not config .cache_dir :
82
+ config .cache_dir = os .path .join (scratch_dir , "uv-cache" )
83
+ if not os .path .exists (config .cache_dir ):
84
+ self ._osutils .makedirs (config .cache_dir )
85
+
79
86
def sync_dependencies (
80
87
self ,
81
88
target_dir : str ,
82
89
scratch_dir : str ,
83
90
config : Optional [UvConfig ] = None ,
84
91
python_version : Optional [str ] = None ,
85
- platform : Optional [str ] = None ,
86
- architecture : Optional [str ] = None ,
87
92
manifest_path : Optional [str ] = None ,
88
93
project_dir : Optional [str ] = None ,
89
94
) -> None :
@@ -95,8 +100,6 @@ def sync_dependencies(
95
100
scratch_dir: Scratch directory for temporary operations
96
101
config: UV configuration options
97
102
python_version: Target Python version (e.g., "3.9")
98
- platform: Target platform (e.g., "linux")
99
- architecture: Target architecture (e.g., "x86_64")
100
103
manifest_path: Path to dependency manifest file (for backwards compatibility)
101
104
project_dir: Project directory containing pyproject.toml and uv.lock
102
105
"""
@@ -113,11 +116,7 @@ def sync_dependencies(
113
116
raise ValueError ("Either project_dir or manifest_path must be provided" )
114
117
115
118
# Ensure UV cache is configured to use scratch directory
116
- if not config .cache_dir :
117
- config .cache_dir = os .path .join (scratch_dir , "uv-cache" )
118
- # Use exist_ok equivalent for osutils
119
- if not os .path .exists (config .cache_dir ):
120
- self ._osutils .makedirs (config .cache_dir )
119
+ self ._ensure_cache_dir (config , scratch_dir )
121
120
122
121
args = ["sync" ]
123
122
@@ -136,6 +135,8 @@ def sync_dependencies(
136
135
137
136
if rc != 0 :
138
137
raise UvInstallationError (reason = f"UV sync failed: { stderr } " )
138
+
139
+ LOG .debug ("UV sync completed successfully: %s" , stdout )
139
140
140
141
# Copy dependencies from virtual environment to target directory
141
142
# uv sync creates a .venv directory in the project directory
@@ -180,11 +181,7 @@ def install_requirements(
180
181
config = UvConfig ()
181
182
182
183
# Ensure UV cache is configured to use scratch directory
183
- if not config .cache_dir :
184
- config .cache_dir = os .path .join (scratch_dir , "uv-cache" )
185
- # Use exist_ok equivalent for osutils
186
- if not os .path .exists (config .cache_dir ):
187
- self ._osutils .makedirs (config .cache_dir )
184
+ self ._ensure_cache_dir (config , scratch_dir )
188
185
189
186
args = ["pip" , "install" ]
190
187
@@ -218,6 +215,8 @@ def install_requirements(
218
215
219
216
if rc != 0 :
220
217
raise UvInstallationError (reason = f"UV pip install failed: { stderr } " )
218
+
219
+ LOG .debug ("UV pip install completed successfully: %s" , stdout )
221
220
222
221
223
222
class PythonUvDependencyBuilder :
@@ -263,12 +262,9 @@ def build_dependencies(
263
262
264
263
# Configure UV to use scratch directory for cache if not already set
265
264
if not config .cache_dir :
266
- uv_cache_dir = os .path .join (scratch_dir_path , "uv-cache" )
267
- # Use exist_ok equivalent for osutils
268
- if not os .path .exists (uv_cache_dir ):
269
- self ._osutils .makedirs (uv_cache_dir )
270
- config .cache_dir = uv_cache_dir
271
- LOG .debug ("Configured UV cache directory: %s" , uv_cache_dir )
265
+ config .cache_dir = os .path .join (scratch_dir_path , "uv-cache" )
266
+ if not os .path .exists (config .cache_dir ):
267
+ self ._osutils .makedirs (config .cache_dir )
272
268
273
269
# Determine Python version from runtime
274
270
python_version = self ._extract_python_version (self .runtime )
@@ -277,19 +273,31 @@ def build_dependencies(
277
273
manifest_name = os .path .basename (manifest_path )
278
274
279
275
try :
280
- # Get the appropriate handler for this manifest
276
+ # Get the appropriate build handler based on manifest type
277
+ # This dispatch system allows different build strategies:
278
+ # - pyproject.toml: Uses uv sync (with lock) or uv lock + export (without lock)
279
+ # - requirements.txt: Uses uv pip install with platform targeting
281
280
handler = self ._get_manifest_handler (manifest_name )
282
281
283
- # Execute the handler
282
+ # Execute the selected build strategy
283
+ # Dependencies are built by the handler methods which call UvRunner:
284
+ # - UvRunner.sync_dependencies(): For pyproject.toml with uv.lock
285
+ # - UvRunner.pip_install(): For requirements.txt and pyproject.toml without lock
286
+ # Both methods install packages and copy site-packages to artifacts_dir_path
284
287
handler (manifest_path , artifacts_dir_path , scratch_dir_path , python_version , architecture , config )
285
288
286
289
except Exception as e :
287
290
LOG .error ("Failed to build dependencies: %s" , str (e ))
288
291
raise
289
292
290
293
def _get_manifest_handler (self , manifest_name : str ):
291
- """Get the appropriate handler function for a manifest file."""
292
- # Exact match handlers for ACTUAL manifests
294
+ """Get the appropriate handler function for a manifest file.
295
+
296
+ Uses a dictionary-based dispatch pattern for extensibility.
297
+ This allows easy addition of new manifest types in the future
298
+ without modifying the core dispatch logic.
299
+ """
300
+ # Exact match handlers for specific manifest files
293
301
exact_handlers = {
294
302
"pyproject.toml" : self ._handle_pyproject_build ,
295
303
}
@@ -417,7 +425,16 @@ def _build_from_pyproject(
417
425
def _export_pyproject_to_requirements (
418
426
self , pyproject_path : str , scratch_dir : str , python_version : str
419
427
) -> Optional [str ]:
420
- """Use UV's native lock and export to convert pyproject.toml to requirements.txt."""
428
+ """Use UV's native lock and export to convert pyproject.toml to requirements.txt.
429
+
430
+ This conversion is necessary when pyproject.toml exists without a uv.lock file.
431
+ UV's pip install command provides better platform targeting capabilities
432
+ (--python-platform) compared to uv sync, which is essential for Lambda's
433
+ cross-platform builds (x86_64/ARM64). The workflow is:
434
+ 1. uv lock: Generate lock file from pyproject.toml
435
+ 2. uv export: Convert lock file to requirements.txt format
436
+ 3. Use requirements.txt with uv pip install for platform-specific builds
437
+ """
421
438
project_dir = os .path .dirname (pyproject_path )
422
439
423
440
try :
@@ -492,7 +509,7 @@ def _build_from_requirements(
492
509
except Exception as e :
493
510
raise UvBuildError (reason = f"Failed to build from requirements: { str (e )} " )
494
511
495
- def _extract_python_version (self , runtime : str ) -> str :
512
+ def _extract_python_version (self , runtime : Optional [ str ] ) -> str :
496
513
"""Extract Python version from runtime string."""
497
514
if not runtime :
498
515
raise UvBuildError (reason = "Runtime is required but was not provided" )
0 commit comments