@@ -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 \n STDERR:\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