diff --git a/aws_lambda_builders/workflows/python_uv/packager.py b/aws_lambda_builders/workflows/python_uv/packager.py index fc2ef696f..07519efa3 100644 --- a/aws_lambda_builders/workflows/python_uv/packager.py +++ b/aws_lambda_builders/workflows/python_uv/packager.py @@ -136,8 +136,10 @@ def install_requirements( # Add requirements file args.extend(["-r", requirements_path]) - # Add target directory - args.extend(["--target", target_dir]) + # Resolve --target to an absolute path: UV runs with cwd set to the project directory, so a + # relative target (e.g. the incremental-build dependencies dir) would otherwise be created + # under the source directory instead of the build root. + args.extend(["--target", os.path.abspath(target_dir)]) # Add configuration arguments args.extend(config.to_uv_args()) diff --git a/aws_lambda_builders/workflows/python_uv/workflow.py b/aws_lambda_builders/workflows/python_uv/workflow.py index 2f924fef2..374c6beed 100644 --- a/aws_lambda_builders/workflows/python_uv/workflow.py +++ b/aws_lambda_builders/workflows/python_uv/workflow.py @@ -139,13 +139,15 @@ def _setup_build_actions(self, source_dir, artifacts_dir, scratch_dir, manifest_ ) ) - # Advanced case: Copy dependencies from dependencies_dir to artifacts_dir if configured + # Advanced case: copy dependencies from dependencies_dir to artifacts_dir if configured. + # Dependencies were installed into dependencies_dir above, so that is the copy source + # (artifact_dir) and artifacts_dir is the destination. if self.dependencies_dir and self.combine_dependencies: self.actions.append( CopyDependenciesAction( source_dir=source_dir, - artifact_dir=artifacts_dir, - destination_dir=self.dependencies_dir, + artifact_dir=self.dependencies_dir, + destination_dir=artifacts_dir, maintain_symlinks=False, ) ) diff --git a/tests/integration/workflows/python_uv/test_python_uv.py b/tests/integration/workflows/python_uv/test_python_uv.py index 3b55a4fbd..81af2adfc 100644 --- a/tests/integration/workflows/python_uv/test_python_uv.py +++ b/tests/integration/workflows/python_uv/test_python_uv.py @@ -121,6 +121,9 @@ def test_workflow_with_dependencies_dir(self): dependencies_files = set(os.listdir(self.dependencies_dir)) self.assertEqual(expected_dependencies.intersection(dependencies_files), expected_dependencies) + # combine_dependencies defaults to True, so dependencies must also be copied into the artifacts dir. + self.assertEqual(expected_dependencies.intersection(output_files), expected_dependencies) + @skipIf(which("uv") is None, "uv not available") def test_workflow_builds_numpy_successfully(self): """Test that UV can build numpy (same as PIP workflow test)""" diff --git a/tests/unit/workflows/python_uv/test_packager.py b/tests/unit/workflows/python_uv/test_packager.py index d370c3f9c..1433ce84b 100644 --- a/tests/unit/workflows/python_uv/test_packager.py +++ b/tests/unit/workflows/python_uv/test_packager.py @@ -125,6 +125,22 @@ def test_install_requirements_success(self): self.assertIn("-r", args_called) self.assertIn("/path/to/requirements.txt", args_called) + def test_install_requirements_resolves_relative_target_to_absolute(self): + # UV runs with cwd=project_dir, so a relative --target must be resolved to an absolute path + # first, otherwise dependencies land under the source dir instead of the build root. + self.mock_subprocess_uv.run_uv_command.return_value = (0, "success", "") + + self.uv_runner.install_requirements( + requirements_path="/path/to/requirements.txt", + target_dir=os.path.join(".aws-sam", "deps", "abc-123"), + scratch_dir="/scratch", + ) + + args_called = self.mock_subprocess_uv.run_uv_command.call_args[0][0] + target_value = args_called[args_called.index("--target") + 1] + self.assertTrue(os.path.isabs(target_value), f"--target should be absolute, got: {target_value}") + self.assertEqual(target_value, os.path.abspath(os.path.join(".aws-sam", "deps", "abc-123"))) + def test_install_requirements_failure(self): self.mock_subprocess_uv.run_uv_command.return_value = (1, "", "error message") diff --git a/tests/unit/workflows/python_uv/test_workflow.py b/tests/unit/workflows/python_uv/test_workflow.py index 3c54e1714..fbec50153 100644 --- a/tests/unit/workflows/python_uv/test_workflow.py +++ b/tests/unit/workflows/python_uv/test_workflow.py @@ -74,6 +74,13 @@ def test_workflow_sets_up_actions_with_dependencies_dir(self): self.assertIsInstance(self.workflow.actions[2], CopyDependenciesAction) self.assertIsInstance(self.workflow.actions[3], CopySourceAction) + # Dependencies are installed into dependencies_dir, then copied into artifacts_dir; + # artifact_dir is the copy source and dest_dir the destination (must not be swapped). + copy_deps_action = self.workflow.actions[2] + self.assertEqual(copy_deps_action.source_dir, "source") + self.assertEqual(copy_deps_action.artifact_dir, "deps") + self.assertEqual(copy_deps_action.dest_dir, "artifacts") + def test_workflow_sets_up_actions_without_download_dependencies_and_dependencies_dir(self): self.workflow = PythonUvWorkflow( "source",