Skip to content

Commit cb25d37

Browse files
author
Taniya Mathur
committed
updating build script to handle lib import error in lambda
1 parent 765efd9 commit cb25d37

File tree

2 files changed

+176
-39
lines changed

2 files changed

+176
-39
lines changed

lib/idp_common_pkg/tests/unit/test_publish.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -951,11 +951,11 @@ def test_clean_and_build_success(self, mock_rmtree, mock_exists, mock_run):
951951
"sam",
952952
"build",
953953
"--template-file",
954-
"patterns/pattern-1/template.yaml",
954+
"template.yaml", # Now uses basename
955955
"--cached",
956956
"--parallel",
957957
],
958-
cwd="patterns/pattern-1",
958+
cwd=os.path.abspath("patterns/pattern-1"), # Now uses absolute path
959959
)
960960

961961
@patch("subprocess.run")
@@ -1048,6 +1048,9 @@ def test_run_minimal_success_flow(self):
10481048
patch.object(publisher, "setup_artifacts_bucket") as mock_setup_bucket,
10491049
patch.object(publisher, "clean_temp_files"),
10501050
patch.object(publisher, "clean_lib"),
1051+
patch.object(
1052+
publisher, "build_idp_common_package", return_value=True
1053+
), # Add this mock
10511054
patch.object(publisher, "needs_rebuild", return_value=False),
10521055
patch.object(
10531056
publisher, "build_patterns_concurrently", return_value=True
@@ -1127,6 +1130,9 @@ def test_run_pattern_build_failure(self):
11271130
patch.object(publisher, "setup_artifacts_bucket"),
11281131
patch.object(publisher, "clean_temp_files"),
11291132
patch.object(publisher, "clean_lib"),
1133+
patch.object(
1134+
publisher, "build_idp_common_package", return_value=True
1135+
), # Add this mock
11301136
patch.object(publisher, "needs_rebuild", return_value=False),
11311137
patch.object(publisher, "build_patterns_concurrently", return_value=False),
11321138
patch.object(publisher.console, "print") as mock_print,
@@ -1169,7 +1175,9 @@ def test_sam_build_linux(self, mock_run, mock_system):
11691175
"--cached",
11701176
"--parallel",
11711177
]
1172-
mock_run.assert_called_with(expected_cmd, cwd=".")
1178+
# The method now uses absolute paths for thread safety
1179+
expected_cwd = os.path.abspath(".")
1180+
mock_run.assert_called_with(expected_cmd, cwd=expected_cwd)
11731181

11741182
@patch("platform.system")
11751183
@patch("subprocess.run")
@@ -1193,7 +1201,9 @@ def test_sam_build_windows(self, mock_run, mock_system):
11931201
"--cached",
11941202
"--parallel",
11951203
]
1196-
mock_run.assert_called_with(expected_cmd, cwd=".")
1204+
# The method now uses absolute paths for thread safety
1205+
expected_cwd = os.path.abspath(".")
1206+
mock_run.assert_called_with(expected_cmd, cwd=expected_cwd)
11971207

11981208
@patch("platform.system")
11991209
@patch("subprocess.run")
@@ -1216,7 +1226,9 @@ def test_sam_build_macos(self, mock_run, mock_system):
12161226
"--cached",
12171227
"--parallel",
12181228
]
1219-
mock_run.assert_called_with(expected_cmd, cwd=".")
1229+
# The method now uses absolute paths for thread safety
1230+
expected_cwd = os.path.abspath(".")
1231+
mock_run.assert_called_with(expected_cmd, cwd=expected_cwd)
12201232

12211233
@patch("platform.system")
12221234
def test_path_handling_windows(self, mock_system):

publish.py

Lines changed: 159 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -581,8 +581,118 @@ def clean_lib(self):
581581
if file_name.endswith(".pyc"):
582582
os.remove(os.path.join(root, file_name))
583583

584+
def build_idp_common_package(self):
585+
"""Build the idp_common package to ensure it's available for Lambda functions"""
586+
lib_pkg_dir = "./lib/idp_common_pkg"
587+
588+
if not os.path.exists(lib_pkg_dir):
589+
self.console.print(
590+
f"[yellow]Warning: {lib_pkg_dir} directory not found[/yellow]"
591+
)
592+
return
593+
594+
self.console.print(
595+
"[cyan]Building idp_common package for Lambda functions[/cyan]"
596+
)
597+
598+
try:
599+
# Build the package in development mode so it's available for local imports
600+
cmd = ["pip", "install", "-e", ".", "--quiet"]
601+
result = subprocess.run(
602+
cmd, cwd=lib_pkg_dir, capture_output=True, text=True
603+
)
604+
605+
if result.returncode != 0:
606+
self.log_verbose(
607+
f"pip install failed, trying alternative approach: {result.stderr}"
608+
)
609+
# Alternative: build the package using setup.py
610+
cmd = ["python", "setup.py", "build"]
611+
result = subprocess.run(
612+
cmd, cwd=lib_pkg_dir, capture_output=True, text=True
613+
)
614+
615+
if result.returncode != 0:
616+
error_output = (
617+
f"STDOUT:\n{result.stdout}\n\nSTDERR:\n{result.stderr}"
618+
)
619+
self.log_error_details("idp_common package build", error_output)
620+
return False
621+
622+
self.console.print(
623+
"[green]✅ idp_common package built successfully[/green]"
624+
)
625+
return True
626+
627+
except Exception as e:
628+
self.console.print(f"[red]Error building idp_common package: {e}[/red]")
629+
return False
630+
631+
def fix_requirements_paths(self, directory):
632+
"""Fix relative paths in requirements.txt files to work with SAM build from project root"""
633+
self.log_verbose(f"Fixing requirements.txt paths in {directory}")
634+
635+
# Get absolute path to the lib directory
636+
project_root = os.path.abspath(".")
637+
lib_abs_path = os.path.join(project_root, "lib", "idp_common_pkg")
638+
639+
# Find all requirements.txt files in the directory
640+
for root, dirs, files in os.walk(directory):
641+
for file in files:
642+
if file == "requirements.txt":
643+
req_file_path = os.path.join(root, file)
644+
self.log_verbose(f"Processing requirements file: {req_file_path}")
645+
646+
try:
647+
# Read the current requirements
648+
with open(req_file_path, "r") as f:
649+
content = f.read()
650+
651+
# Check if it contains relative paths to lib
652+
if "lib/idp_common_pkg" in content and not content.startswith(
653+
"-e "
654+
):
655+
# Replace any existing lib paths with absolute path
656+
import re
657+
658+
# Pattern to match various forms of lib path references with extras
659+
# Only match if not already processed (doesn't start with -e)
660+
pattern = r"^([./]*lib/idp_common_pkg(\[[^\]]+\])?)"
661+
662+
def replace_lib_path(match):
663+
extras_match = re.search(
664+
r"\[([^\]]+)\]", match.group(1)
665+
)
666+
extras = (
667+
f"[{extras_match.group(1)}]" if extras_match else ""
668+
)
669+
# Use absolute path with -e flag for editable install
670+
return f"-e {lib_abs_path}{extras}"
671+
672+
new_content = re.sub(
673+
pattern, replace_lib_path, content, flags=re.MULTILINE
674+
)
675+
676+
if new_content != content:
677+
self.log_verbose(
678+
f"Updated requirements.txt: {req_file_path}"
679+
)
680+
self.log_verbose(f" Old content: {content.strip()}")
681+
self.log_verbose(
682+
f" New content: {new_content.strip()}"
683+
)
684+
685+
# Write the updated content
686+
with open(req_file_path, "w") as f:
687+
f.write(new_content)
688+
689+
except Exception as e:
690+
self.log_verbose(f"Error processing {req_file_path}: {e}")
691+
# Don't fail the build for this, just log the error
692+
continue
693+
584694
def clean_and_build(self, template_path):
585-
"""Clean previous build artifacts and run sam build with optimizations"""
695+
"""Clean previous build artifacts and run sam build (matching publish_2.sh approach)"""
586696
dir_path = os.path.dirname(template_path)
587697

588698
# If dir_path is empty (template.yaml in current directory), use current directory
@@ -605,41 +715,60 @@ def clean_and_build(self, template_path):
605715
print(f"Clearing SAM cache in {cache_dir} (lib changed)")
606716
shutil.rmtree(cache_dir)
607717

608-
# Run sam build with optimizations
718+
# Run sam build with cwd parameter (thread-safe)
719+
abs_dir_path = os.path.abspath(dir_path)
609720
cmd = [
610721
"sam",
611722
"build",
612723
"--template-file",
613-
template_path,
724+
os.path.basename(template_path),
614725
"--cached",
615726
"--parallel",
616727
]
617-
if self.use_container_flag:
728+
if self.use_container_flag and self.use_container_flag.strip():
618729
cmd.append(self.use_container_flag)
619730

620-
result = subprocess.run(cmd, cwd=dir_path)
731+
result = subprocess.run(cmd, cwd=abs_dir_path)
621732
if result.returncode != 0:
622733
# If cached build fails, try without cache
623734
print(f"Cached build failed, retrying without cache for {template_path}")
624735
cmd_no_cache = [c for c in cmd if c != "--cached"]
625-
result = subprocess.run(cmd_no_cache, cwd=dir_path)
736+
result = subprocess.run(cmd_no_cache, cwd=abs_dir_path)
626737
if result.returncode != 0:
627-
print(f"Error running sam build in {dir_path}")
738+
print("Error running sam build")
628739
sys.exit(1)
629740

630741
def build_and_package_template(self, directory):
631-
"""Build and package a template directory (optimized for progress display)"""
742+
"""Build and package a template directory (using same approach as publish_2.sh)"""
632743
if self.needs_rebuild(directory):
633744
# Use absolute paths to avoid directory changing issues
634745
abs_directory = os.path.abspath(directory)
635-
template_path = os.path.join(abs_directory, "template.yaml")
636746

637747
# Track build time
638748
build_start = time.time()
639749

640750
try:
641-
# Build the template using absolute paths with optimizations
642-
cmd = ["sam", "build", "--template-file", template_path]
751+
# Clean previous build artifacts if lib changed (thread-safe)
752+
lib_changed = hasattr(self, "_lib_changed") and self._lib_changed
753+
aws_sam_dir = os.path.join(abs_directory, ".aws-sam")
754+
build_dir = os.path.join(aws_sam_dir, "build")
755+
756+
if lib_changed and os.path.exists(aws_sam_dir):
757+
if os.path.exists(build_dir):
758+
self.log_verbose(
759+
f"Cleaning build artifacts in {build_dir} (lib changed)"
760+
)
761+
shutil.rmtree(build_dir)
762+
# Also clear SAM cache when lib changes
763+
cache_dir = os.path.join(aws_sam_dir, "cache")
764+
if os.path.exists(cache_dir):
765+
self.log_verbose(
766+
f"Clearing SAM cache in {cache_dir} (lib changed)"
767+
)
768+
shutil.rmtree(cache_dir)
769+
770+
# Build the template from the pattern directory
771+
cmd = ["sam", "build", "--template-file", "template.yaml"]
643772

644773
# Add performance optimizations
645774
cmd.extend(
@@ -649,26 +778,14 @@ def build_and_package_template(self, directory):
649778
]
650779
)
651780

652-
if self.use_container_flag:
781+
if self.use_container_flag and self.use_container_flag.strip():
653782
cmd.append(self.use_container_flag)
654783

655-
# Only clean build artifacts if we're doing a fresh build
656-
# For cached builds, we want to preserve the cache
657-
aws_sam_dir = os.path.join(abs_directory, ".aws-sam")
658-
build_dir = os.path.join(aws_sam_dir, "build")
659-
660-
# Check if we should do a clean build (when lib changed or forced)
661-
lib_changed = hasattr(self, "_lib_changed") and self._lib_changed
662-
if lib_changed and os.path.exists(build_dir):
663-
# Only clean if lib changed to force rebuild with new dependencies
664-
shutil.rmtree(build_dir)
665-
# Also clear SAM cache when lib changes
666-
cache_dir = os.path.join(aws_sam_dir, "cache")
667-
if os.path.exists(cache_dir):
668-
shutil.rmtree(cache_dir)
669-
670784
sam_build_start = time.time()
671-
self.log_verbose(f"Running SAM build command: {' '.join(cmd)}")
785+
self.log_verbose(
786+
f"Running SAM build command in {directory}: {' '.join(cmd)}"
787+
)
788+
# Run SAM build from the pattern directory (like publish_2.sh)
672789
result = subprocess.run(
673790
cmd, cwd=abs_directory, capture_output=True, text=True
674791
)
@@ -685,7 +802,7 @@ def build_and_package_template(self, directory):
685802
cmd_no_cache = [c for c in cmd if c != "--cached"]
686803
sam_build_start = time.time()
687804
self.log_verbose(
688-
f"Running SAM build command (no cache): {' '.join(cmd_no_cache)}"
805+
f"Running SAM build command (no cache) in {directory}: {' '.join(cmd_no_cache)}"
689806
)
690807
result = subprocess.run(
691808
cmd_no_cache, cwd=abs_directory, capture_output=True, text=True
@@ -701,7 +818,7 @@ def build_and_package_template(self, directory):
701818
)
702819
return False
703820

704-
# Package the template
821+
# Package the template (using absolute paths)
705822
build_template_path = os.path.join(
706823
abs_directory, ".aws-sam", "build", "template.yaml"
707824
)
@@ -724,9 +841,8 @@ def build_and_package_template(self, directory):
724841

725842
sam_package_start = time.time()
726843
self.log_verbose(f"Running SAM package command: {' '.join(cmd)}")
727-
result = subprocess.run(
728-
cmd, cwd=abs_directory, capture_output=True, text=True
729-
)
844+
# Run SAM package from project root (no cwd change needed)
845+
result = subprocess.run(cmd, capture_output=True, text=True)
730846
sam_package_time = time.time() - sam_package_start
731847

732848
if result.returncode != 0:
@@ -744,7 +860,11 @@ def build_and_package_template(self, directory):
744860
f"[dim] {pattern_name}: build={sam_build_time:.1f}s, package={sam_package_time:.1f}s, total={total_time:.1f}s[/dim]"
745861
)
746862

747-
except Exception:
863+
except Exception as e:
864+
import traceback
865+
866+
self.log_verbose(f"Exception in build_and_package_template: {e}")
867+
self.log_verbose(f"Traceback: {traceback.format_exc()}")
748868
return False
749869

750870
# Update the checksum
@@ -1256,6 +1376,11 @@ def run(self, args):
12561376
# Clean lib artifacts
12571377
self.clean_lib()
12581378

1379+
# Build idp_common package to ensure it's available for Lambda functions
1380+
if not self.build_idp_common_package():
1381+
self.console.print("[red]❌ Failed to build idp_common package[/red]")
1382+
sys.exit(1)
1383+
12591384
# Check if lib has changed
12601385
lib_changed = self.needs_rebuild("./lib")
12611386
self._lib_changed = (

0 commit comments

Comments
 (0)