@@ -494,7 +494,6 @@ def clean_and_build(self, template_path):
494494 "build" ,
495495 "--template-file" ,
496496 template_filename ,
497- "--cached" ,
498497 ]
499498
500499 # Only add --parallel if no idp_common dependencies to prevent race conditions
@@ -504,17 +503,10 @@ def clean_and_build(self, template_path):
504503 if self .use_container_flag and self .use_container_flag .strip ():
505504 cmd .append (self .use_container_flag )
506505
507- result = subprocess .run (cmd , cwd = abs_dir_path )
506+ result = subprocess .run (cmd , capture_output = True , text = True )
508507 if result .returncode != 0 :
509- # If cached build fails, try without cache
510- print (
511- f"Cached build failed, retrying without cache for { template_filename } "
512- )
513- cmd_no_cache = [c for c in cmd if c != "--cached" ]
514- result = subprocess .run (cmd_no_cache , cwd = abs_dir_path )
515- if result .returncode != 0 :
516- print ("Error running sam build" )
517- sys .exit (1 )
508+ print ("Error running sam build" )
509+ sys .exit (1 )
518510
519511 def _check_template_for_idp_common_deps (self , template_path ):
520512 """Check if a template has Lambda functions with idp_common dependencies."""
@@ -552,15 +544,7 @@ def build_and_package_template(self, directory, force_rebuild=False):
552544 # Build the template from the pattern directory
553545 cmd = ["sam" , "build" , "--template-file" , "template.yaml" ]
554546
555- # Add caching but remove parallel flag to avoid race conditions
556- # when building multiple templates concurrently
557- cmd .extend (
558- [
559- "--cached" , # Enable SAM build caching
560- # Note: Removed --parallel to prevent race conditions with idp_common_pkg
561- ]
562- )
563-
547+ # Add container flag if needed
564548 if self .use_container_flag and self .use_container_flag .strip ():
565549 cmd .append (self .use_container_flag )
566550
@@ -575,29 +559,10 @@ def build_and_package_template(self, directory, force_rebuild=False):
575559 sam_build_time = time .time () - sam_build_start
576560
577561 if result .returncode != 0 :
578- # If cached build fails, try without cache
579- self .log_verbose (
580- f"Cached build failed for { directory } , retrying without cache"
581- )
582- self .console .print (
583- f"[yellow]Cached build failed for { directory } , retrying without cache[/yellow]"
584- )
585- cmd_no_cache = [c for c in cmd if c != "--cached" ]
586- sam_build_start = time .time ()
587- self .log_verbose (
588- f"Running SAM build command (no cache) in { directory } : { ' ' .join (cmd_no_cache )} "
589- )
590- result = subprocess .run (
591- cmd_no_cache , cwd = abs_directory , capture_output = True , text = True
592- )
593- sam_build_time = time .time () - sam_build_start
594- if result .returncode != 0 :
595- # Log detailed error information
596- error_output = (
597- f"STDOUT:\n { result .stdout } \n \n STDERR:\n { result .stderr } "
598- )
599- self .log_error_details (f"SAM build for { directory } " , error_output )
600- return False
562+ # Log detailed error information
563+ error_output = f"STDOUT:\n { result .stdout } \n \n STDERR:\n { result .stderr } "
564+ self .log_error_details (f"SAM build for { directory } " , error_output )
565+ return False
601566
602567 # Package the template (using absolute paths)
603568 build_template_path = os .path .join (
@@ -633,6 +598,10 @@ def build_and_package_template(self, directory, force_rebuild=False):
633598 self .log_error_details (f"SAM package for { directory } " , error_output )
634599 return False
635600
601+ # Log S3 upload location
602+ template_url = f"https://s3.{ self .region } .amazonaws.com/{ self .bucket } /{ self .prefix } /{ directory } /template.yaml"
603+ self .console .print (f"[dim] 📤 Template uploaded: { template_url } [/dim]" )
604+
636605 # Log timing information
637606 total_time = time .time () - build_start
638607 pattern_name = os .path .basename (directory )
@@ -1139,7 +1108,9 @@ def _scan_lambda_directory(self, src_dir, template_path, context):
11391108 function_key = f"{ context } /{ func_dir .name } "
11401109 functions [function_key ] = {
11411110 "template_path" : template_path ,
1142- "function_name" : self ._extract_function_name (func_dir .name ),
1111+ "function_name" : self ._extract_function_name (
1112+ func_dir .name , template_path
1113+ ),
11431114 "source_path" : func_dir ,
11441115 "context" : context ,
11451116 "template_dir" : template_path .parent ,
@@ -1171,34 +1142,66 @@ def _check_requirements_has_idp_common_pkg(self, func_dir):
11711142 except Exception as e :
11721143 return False , f"Error reading requirements.txt: { e } "
11731144
1174- def _extract_function_name (self , dir_name ):
1175- """Extract CloudFormation function name from directory name."""
1176- name_mappings = {
1177- # Pattern functions
1178- "bda_invoke_function" : "InvokeBDAFunction" ,
1179- "bda_completion_function" : "BDACompletionFunction" ,
1180- "processresults_function" : "ProcessResultsFunction" ,
1181- "summarization_function" : "SummarizationFunction" ,
1182- "hitl-process-function" : "HITLProcessLambdaFunction" ,
1183- "hitl-wait-function" : "HITLWaitFunction" ,
1184- "hitl-status-update-function" : "HITLStatusUpdateFunction" ,
1185- "ocr_function" : "OCRFunction" ,
1186- "classification_function" : "ClassificationFunction" ,
1187- "extraction_function" : "ExtractionFunction" ,
1188- "assessment_function" : "AssessmentFunction" ,
1189- # Main template functions
1190- "queue_processor" : "QueueProcessor" ,
1191- "workflow_tracker" : "WorkflowTracker" ,
1192- "evaluation_function" : "EvaluationFunction" ,
1193- "save_reporting_data" : "SaveReportingDataFunction" ,
1194- "queue_sender" : "QueueSender" ,
1195- "copy_to_baseline_resolver" : "CopyToBaselineResolverFunction" ,
1196- # Agent-related functions
1197- "agent_processor" : "AgentProcessorFunction" ,
1198- "list_available_agents" : "ListAvailableAgentsFunction" ,
1199- "agent_request_handler" : "AgentRequestHandlerFunction" ,
1200- }
1201- return name_mappings .get (dir_name , dir_name )
1145+ def _extract_function_name (self , dir_name , template_path ):
1146+ """Extract CloudFormation function name from template by matching CodeUri."""
1147+ try :
1148+ import yaml
1149+
1150+ # Create a custom loader that ignores CloudFormation intrinsic functions
1151+ class CFLoader (yaml .SafeLoader ):
1152+ pass
1153+
1154+ def construct_unknown (loader , node ):
1155+ return None
1156+
1157+ # Add constructors for CloudFormation intrinsic functions
1158+ cf_functions = [
1159+ "!Ref" ,
1160+ "!GetAtt" ,
1161+ "!Join" ,
1162+ "!Sub" ,
1163+ "!Select" ,
1164+ "!Split" ,
1165+ "!Base64" ,
1166+ "!GetAZs" ,
1167+ "!ImportValue" ,
1168+ "!FindInMap" ,
1169+ "!Equals" ,
1170+ "!And" ,
1171+ "!Or" ,
1172+ "!Not" ,
1173+ "!If" ,
1174+ "!Condition" ,
1175+ ]
1176+
1177+ for func in cf_functions :
1178+ CFLoader .add_constructor (func , construct_unknown )
1179+
1180+ with open (template_path , "r" ) as f :
1181+ template = yaml .load (f , Loader = CFLoader )
1182+
1183+ resources = template .get ("Resources" , {})
1184+ for resource_name , resource_config in resources .items ():
1185+ if (
1186+ resource_config
1187+ and resource_config .get ("Type" ) == "AWS::Serverless::Function"
1188+ ):
1189+ properties = resource_config .get ("Properties" , {})
1190+ if properties :
1191+ code_uri = properties .get ("CodeUri" , "" )
1192+ if isinstance (code_uri , str ):
1193+ code_uri = code_uri .rstrip ("/" )
1194+ code_dir = (
1195+ code_uri .split ("/" )[- 1 ] if "/" in code_uri else code_uri
1196+ )
1197+ if code_dir == dir_name :
1198+ return resource_name
1199+
1200+ return dir_name
1201+
1202+ except Exception as e :
1203+ self .log_verbose (f"Error reading template { template_path } : { e } " )
1204+ return dir_name
12021205
12031206 def _validate_idp_common_in_build (self , template_dir , function_name , source_path ):
12041207 """Validate that idp_common package exists in the built Lambda function."""
@@ -1222,24 +1225,6 @@ def _validate_idp_common_in_build(self, template_dir, function_name, source_path
12221225 if not file_path .exists ():
12231226 issues .append (f"Missing core file: { core_file } " )
12241227
1225- # Check for key modules based on function type
1226- module_checks = {
1227- "InvokeBDAFunction" : ["bda/bda_service.py" , "bda/__init__.py" ],
1228- "BDACompletionFunction" : ["metrics/__init__.py" ],
1229- "ProcessResultsFunction" : ["ocr/service.py" , "extraction/service.py" ],
1230- "ClassificationFunction" : ["classification/service.py" ],
1231- "ExtractionFunction" : ["extraction/service.py" ],
1232- "OCRFunction" : ["ocr/service.py" ],
1233- "AssessmentFunction" : ["assessment/service.py" ],
1234- "SummarizationFunction" : ["summarization/service.py" ],
1235- }
1236-
1237- if function_name in module_checks :
1238- for module_path in module_checks [function_name ]:
1239- module_file = idp_common_dir / module_path
1240- if not module_file .exists ():
1241- issues .append (f"Missing function-specific module: { module_path } " )
1242-
12431228 return len (issues ) == 0 , issues
12441229
12451230 def _test_import_functionality (self , template_dir , function_name ):
@@ -1333,6 +1318,9 @@ def package_ui(self):
13331318 ui_hash = self .compute_ui_hash ()
13341319 zipfile_name = f"src-{ ui_hash [:16 ]} .zip"
13351320
1321+ # Ensure .aws-sam directory exists
1322+ os .makedirs (".aws-sam" , exist_ok = True )
1323+
13361324 # Check if we need to rebuild
13371325 existing_zipfiles = [
13381326 f
@@ -1506,35 +1494,70 @@ def build_main_template(self, webui_zipfile, components_needing_rebuild):
15061494 else :
15071495 self .console .print ("[green]✅ Main template is up to date[/green]" )
15081496
1509- # Always upload the final template to S3, regardless of whether rebuild was needed
1497+ # Upload main template based on whether build was required
15101498 final_template_key = f"{ self .prefix } /{ self .main_template } "
15111499 packaged_template_path = ".aws-sam/idp-main.yaml"
15121500
1513- # Check if packaged template exists, if not we have a problem
1514- if not os .path .exists (packaged_template_path ):
1515- self .console .print (
1516- f"[red]Error: Packaged template not found at { packaged_template_path } [/red]"
1517- )
1518- self .console .print (
1519- "[red]This suggests the template was never built. Try running without cache.[/red]"
1520- )
1521- sys .exit (1 )
1501+ if main_needs_build :
1502+ # Main was rebuilt, so upload the new template
1503+ if not os .path .exists (packaged_template_path ):
1504+ self .console .print (
1505+ f"[red]Error: Packaged template not found at { packaged_template_path } [/red]"
1506+ )
1507+ sys .exit (1 )
15221508
1523- self .console .print (
1524- f"[cyan]Uploading main template to S3: { final_template_key } [/cyan]"
1525- )
1526- self .log_verbose (f"Local template path: { packaged_template_path } " )
1527- try :
1528- self .s3_client .upload_file (
1529- packaged_template_path ,
1530- self .bucket ,
1531- final_template_key ,
1532- ExtraArgs = {"ACL" : self .acl },
1509+ self .console .print (
1510+ f"[cyan]Uploading main template to S3: { final_template_key } [/cyan]"
15331511 )
1534- self .console .print ("[green]✅ Main template uploaded successfully[/green]" )
1535- except ClientError as e :
1536- self .console .print (f"[red]Error uploading main template: { e } [/red]" )
1537- sys .exit (1 )
1512+ try :
1513+ self .s3_client .upload_file (
1514+ packaged_template_path ,
1515+ self .bucket ,
1516+ final_template_key ,
1517+ ExtraArgs = {"ACL" : self .acl },
1518+ )
1519+ self .console .print (
1520+ "[green]✅ Main template uploaded successfully[/green]"
1521+ )
1522+ except Exception as e :
1523+ self .console .print (f"[red]Failed to upload main template: { e } [/red]" )
1524+ sys .exit (1 )
1525+ else :
1526+ # Main was not rebuilt, check if template exists in S3
1527+ try :
1528+ self .s3_client .head_object (Bucket = self .bucket , Key = final_template_key )
1529+ self .console .print (
1530+ "[green]✅ Main template already exists in S3[/green]"
1531+ )
1532+ except self .s3_client .exceptions .NoSuchKey :
1533+ self .console .print (
1534+ f"[yellow]Main template missing from S3, uploading: { final_template_key } [/yellow]"
1535+ )
1536+ if not os .path .exists (packaged_template_path ):
1537+ self .console .print (
1538+ f"[red]Error: No packaged template to upload at { packaged_template_path } [/red]"
1539+ )
1540+ sys .exit (1 )
1541+ try :
1542+ self .s3_client .upload_file (
1543+ packaged_template_path ,
1544+ self .bucket ,
1545+ final_template_key ,
1546+ ExtraArgs = {"ACL" : self .acl },
1547+ )
1548+ self .console .print (
1549+ "[green]✅ Main template uploaded successfully[/green]"
1550+ )
1551+ except Exception as e :
1552+ self .console .print (
1553+ f"[red]Failed to upload main template: { e } [/red]"
1554+ )
1555+ sys .exit (1 )
1556+ except Exception as e :
1557+ self .console .print (
1558+ f"[yellow]Could not check S3 template existence: { e } [/yellow]"
1559+ )
1560+ self .console .print ("[yellow]Proceeding without upload[/yellow]" )
15381561
15391562 # Validate the template
15401563 template_url = (
@@ -1768,10 +1791,11 @@ def get_components_needing_rebuild(self):
17681791 }
17691792 )
17701793
1771- if self .verbose :
1772- self .console .print (
1773- f"[yellow]📝 { component } needs rebuild due to changes in: { ', ' .join (deps )} [/yellow]"
1774- )
1794+ self .console .print (
1795+ f"[yellow]📝 { component } needs rebuild due to changes in any of these dependencies:[/yellow]"
1796+ )
1797+ for dep in deps :
1798+ self .console .print (f"[yellow] • { dep } [/yellow]" )
17751799
17761800 return components_to_rebuild
17771801
@@ -1783,16 +1807,8 @@ def clear_component_cache(self, component):
17831807 sam_dir = os .path .join (component , ".aws-sam" )
17841808
17851809 if os .path .exists (sam_dir ):
1786- build_dir = os .path .join (sam_dir , "build" )
1787- cache_dir = os .path .join (sam_dir , "cache" )
1788-
1789- if os .path .exists (build_dir ):
1790- self .log_verbose (f"Clearing build cache for { component } : { build_dir } " )
1791- shutil .rmtree (build_dir )
1792-
1793- if os .path .exists (cache_dir ):
1794- self .log_verbose (f"Clearing SAM cache for { component } : { cache_dir } " )
1795- shutil .rmtree (cache_dir )
1810+ self .log_verbose (f"Clearing entire SAM cache for { component } : { sam_dir } " )
1811+ shutil .rmtree (sam_dir )
17961812
17971813 def update_component_checksum (self , components_needing_rebuild ):
17981814 """Update checksum for a successfully built component"""
@@ -1918,7 +1934,27 @@ def run(self, args):
19181934 # Perform smart rebuild detection and cache management
19191935 components_needing_rebuild = self .smart_rebuild_detection ()
19201936
1921- # Clear caches for components that need rebuilding
1937+ # Build lib package if it changed
1938+ lib_changed = any (
1939+ comp ["component" ] == "lib" for comp in components_needing_rebuild
1940+ )
1941+ if lib_changed :
1942+ self .console .print ("[bold yellow]📚 Building lib package[/bold yellow]" )
1943+ lib_dir = "lib/idp_common_pkg"
1944+ result = subprocess .run (
1945+ ["python" , "setup.py" , "build" ],
1946+ cwd = lib_dir ,
1947+ capture_output = True ,
1948+ text = True ,
1949+ )
1950+ if result .returncode != 0 :
1951+ self .console .print (
1952+ f"[red]❌ Failed to build lib package: { result .stderr } [/red]"
1953+ )
1954+ sys .exit (1 )
1955+ self .console .print ("[green]✅ Lib package built successfully[/green]" )
1956+
1957+ # clear component cache
19221958 for comp_info in components_needing_rebuild :
19231959 if comp_info ["component" ] != "lib" : # lib doesnt have sam build
19241960 self .clear_component_cache (comp_info ["component" ])
0 commit comments