@@ -234,6 +234,10 @@ function linuxCompile(ctx)
234234 error (" python build failed" )
235235 end
236236 print (" Build python success!" )
237+
238+ -- Fix shebang lines in Python scripts after successful build
239+ fixShebangLines (path )
240+
237241 print (" Cleaning up ..." )
238242 status = os.execute (" rm -rf " .. dest_pyenv_path )
239243 if status ~= 0 then
@@ -354,39 +358,316 @@ function parseVersionFromPyenv()
354358 end
355359
356360 local versions = jsonObj .versions ;
357-
361+
358362 local numericVersions = {}
359363 local namedVersions = {}
360-
364+
361365 for _ , version in ipairs (versions ) do
362366 if string.match (version , " ^%d" ) then
363367 table.insert (numericVersions , version )
364368 else
365369 table.insert (namedVersions , version )
366370 end
367371 end
368-
372+
369373 table.sort (numericVersions , function (a , b )
370374 return compare_versions (a , b ) > 0
371375 end )
372-
376+
373377 table.sort (namedVersions , function (a , b )
374378 return compare_versions (a , b ) > 0
375379 end )
376-
380+
377381 for _ , version in ipairs (numericVersions ) do
378382 table.insert (result , {
379383 version = version ,
380384 note = " "
381385 })
382386 end
383-
387+
384388 for _ , version in ipairs (namedVersions ) do
385389 table.insert (result , {
386390 version = version ,
387391 note = " "
388392 })
389393 end
390-
394+
391395 return result
392- end
396+ end
397+
398+ -- Fix shebang lines in Python scripts that point to temporary directories
399+ function fixShebangLines (installPath )
400+ return fixShebangForVersion (installPath , nil )
401+ end
402+
403+ -- Fix shebang lines for a specific Python version installation
404+ function fixShebangForVersion (installPath , version )
405+ local versionInfo = version and (" for version " .. version ) or " "
406+ print (" Fixing shebang lines in Python scripts" .. versionInfo .. " ..." )
407+
408+ local binPath = installPath .. " /bin"
409+ local pythonExecutable = installPath .. " /bin/python"
410+
411+ -- Check if bin directory exists
412+ local binDirCheck = io.open (binPath , " r" )
413+ if not binDirCheck then
414+ print (" No bin directory found at " .. binPath .. " , skipping shebang fix" )
415+ return false , 0
416+ end
417+ binDirCheck :close ()
418+
419+ -- Use find command to get all files in bin directory (macOS compatible)
420+ local findCmd = " find " .. binPath .. " -type f -perm +111 2>/dev/null"
421+ local findResult = io.popen (findCmd )
422+ if not findResult then
423+ print (" Could not scan bin directory, skipping shebang fix" )
424+ return false , 0
425+ end
426+
427+ local fixedCount = 0
428+ local checkedCount = 0
429+
430+ -- Process each executable file
431+ for filePath in findResult :lines () do
432+ if filePath and filePath ~= " " then
433+ checkedCount = checkedCount + 1
434+ -- Check if it's a Python script by examining the first line
435+ local file = io.open (filePath , " r" )
436+ if file then
437+ local firstLine = file :read (" *l" )
438+ file :close ()
439+
440+ -- Check if it has a shebang line pointing to a temporary directory
441+ if firstLine and firstLine :match (" ^#!/.*%.version%-fox/temp/[^/]+/" ) then
442+ local filename = filePath :match (" ([^/]+)$" )
443+ print (" Fixing shebang in: " .. filename )
444+ if fixSingleShebang (filePath , pythonExecutable ) then
445+ fixedCount = fixedCount + 1
446+ end
447+ end
448+ end
449+ end
450+ end
451+
452+ findResult :close ()
453+ print (" Shebang fix completed" .. versionInfo .. " . Checked " .. checkedCount .. " files, fixed " .. fixedCount .. " files." )
454+ return true , fixedCount
455+ end
456+
457+ -- Fix shebang line in a single file
458+ function fixSingleShebang (filePath , newPythonPath )
459+ -- Read the entire file
460+ local file = io.open (filePath , " r" )
461+ if not file then
462+ print (" Warning: Could not open file for reading: " .. filePath )
463+ return false
464+ end
465+
466+ local content = file :read (" *all" )
467+ file :close ()
468+
469+ if not content or content == " " then
470+ print (" Warning: File is empty: " .. filePath )
471+ return false
472+ end
473+
474+ -- Replace the shebang line - match any path containing .version-fox/temp/
475+ local newContent , replacements = content :gsub (" ^#!/[^\n ]*%.version%-fox/temp/[^/]+/[^\n ]*" , " #!" .. newPythonPath )
476+
477+ if replacements == 0 then
478+ -- No replacement made, file might not have the problematic shebang
479+ return false
480+ end
481+
482+ -- Create backup of original file
483+ local backupPath = filePath .. " .bak"
484+ local backupFile = io.open (backupPath , " w" )
485+ if backupFile then
486+ backupFile :write (content )
487+ backupFile :close ()
488+ end
489+
490+ -- Write the file back
491+ local file = io.open (filePath , " w" )
492+ if not file then
493+ print (" Warning: Could not open file for writing: " .. filePath )
494+ return false
495+ end
496+
497+ file :write (newContent )
498+ file :close ()
499+
500+ -- Preserve executable permissions
501+ local chmodResult = os.execute (" chmod +x " .. filePath )
502+ if chmodResult ~= 0 then
503+ print (" Warning: Could not set executable permissions on: " .. filePath )
504+ end
505+
506+ -- Remove backup file if everything succeeded
507+ os.remove (backupPath )
508+
509+ return true
510+ end
511+
512+ -- Check Python installation health and return detailed status
513+ function checkPythonHealth (installPath , version )
514+ local versionInfo = version and (" " .. version ) or " "
515+ print (" Checking Python" .. versionInfo .. " installation health..." )
516+
517+ local binPath = installPath .. " /bin"
518+ local pythonExecutable = installPath .. " /bin/python"
519+
520+ local healthReport = {
521+ installPath = installPath ,
522+ version = version ,
523+ binPath = binPath ,
524+ pythonExecutable = pythonExecutable ,
525+ binDirExists = false ,
526+ pythonExists = false ,
527+ scriptsChecked = {},
528+ problemsFound = {},
529+ overallHealth = " unknown"
530+ }
531+
532+ -- Check if bin directory exists
533+ local binDirCheck = io.open (binPath , " r" )
534+ if not binDirCheck then
535+ healthReport .overallHealth = " critical"
536+ table.insert (healthReport .problemsFound , " Bin directory not found: " .. binPath )
537+ return healthReport
538+ end
539+ binDirCheck :close ()
540+ healthReport .binDirExists = true
541+
542+ -- Check if Python executable exists
543+ local pythonCheck = io.open (pythonExecutable , " r" )
544+ if not pythonCheck then
545+ healthReport .overallHealth = " critical"
546+ table.insert (healthReport .problemsFound , " Python executable not found: " .. pythonExecutable )
547+ return healthReport
548+ end
549+ pythonCheck :close ()
550+ healthReport .pythonExists = true
551+
552+ -- Check critical Python scripts
553+ local criticalScripts = {" pip" , " pip3" , " easy_install" }
554+ local problemCount = 0
555+
556+ for _ , scriptName in ipairs (criticalScripts ) do
557+ local scriptPath = binPath .. " /" .. scriptName
558+ local scriptInfo = {
559+ name = scriptName ,
560+ path = scriptPath ,
561+ exists = false ,
562+ executable = false ,
563+ shebangOk = false ,
564+ shebangLine = " "
565+ }
566+
567+ -- Check if script exists
568+ local scriptFile = io.open (scriptPath , " r" )
569+ if scriptFile then
570+ scriptInfo .exists = true
571+ local firstLine = scriptFile :read (" *l" )
572+ scriptFile :close ()
573+
574+ if firstLine then
575+ scriptInfo .shebangLine = firstLine
576+ -- Check if shebang points to temporary directory
577+ if firstLine :match (" ^#!/.*%.version%-fox/temp/[^/]+/" ) then
578+ scriptInfo .shebangOk = false
579+ problemCount = problemCount + 1
580+ table.insert (healthReport .problemsFound , scriptName .. " has problematic shebang: " .. firstLine )
581+ else
582+ scriptInfo .shebangOk = true
583+ end
584+ end
585+
586+ -- Check if script is executable
587+ local execCheck = os.execute (" test -x " .. scriptPath .. " 2>/dev/null" )
588+ scriptInfo .executable = (execCheck == 0 )
589+ if not scriptInfo .executable then
590+ problemCount = problemCount + 1
591+ table.insert (healthReport .problemsFound , scriptName .. " is not executable" )
592+ end
593+ else
594+ table.insert (healthReport .problemsFound , scriptName .. " not found" )
595+ end
596+
597+ table.insert (healthReport .scriptsChecked , scriptInfo )
598+ end
599+
600+ -- Determine overall health
601+ if problemCount == 0 then
602+ healthReport .overallHealth = " healthy"
603+ elseif problemCount <= 2 then
604+ healthReport .overallHealth = " warning"
605+ else
606+ healthReport .overallHealth = " critical"
607+ end
608+
609+ return healthReport
610+ end
611+
612+ -- Fix shebang issues for all installed Python versions
613+ function fixAllPythonVersions (sdkCachePath )
614+ print (" Starting batch fix for all Python installations..." )
615+
616+ if not sdkCachePath then
617+ print (" Error: SDK cache path not provided" )
618+ return false
619+ end
620+
621+ local pythonCachePath = sdkCachePath .. " /python"
622+
623+ -- Check if Python cache directory exists
624+ local pythonCacheCheck = io.open (pythonCachePath , " r" )
625+ if not pythonCacheCheck then
626+ print (" No Python installations found at: " .. pythonCachePath )
627+ return false
628+ end
629+ pythonCacheCheck :close ()
630+
631+ -- Find all Python version directories
632+ local findCmd = " find " .. pythonCachePath .. " -maxdepth 1 -type d -name 'v-*' 2>/dev/null"
633+ local findResult = io.popen (findCmd )
634+ if not findResult then
635+ print (" Could not scan Python installations directory" )
636+ return false
637+ end
638+
639+ local totalFixed = 0
640+ local versionsProcessed = 0
641+
642+ for versionPath in findResult :lines () do
643+ if versionPath and versionPath ~= " " then
644+ local version = versionPath :match (" v%-(.+)$" )
645+ if version then
646+ versionsProcessed = versionsProcessed + 1
647+ print (" \n --- Processing Python " .. version .. " ---" )
648+
649+ -- Check health first
650+ local healthReport = checkPythonHealth (versionPath , version )
651+
652+ if healthReport .overallHealth == " healthy" then
653+ print (" Python " .. version .. " is healthy, skipping" )
654+ else
655+ print (" Python " .. version .. " has " .. # healthReport .problemsFound .. " issues, fixing..." )
656+ local success , fixedCount = fixShebangForVersion (versionPath , version )
657+ if success then
658+ totalFixed = totalFixed + fixedCount
659+ end
660+ end
661+ end
662+ end
663+ end
664+
665+ findResult :close ()
666+
667+ print (" \n === Batch Fix Summary ===" )
668+ print (" Versions processed: " .. versionsProcessed )
669+ print (" Total files fixed: " .. totalFixed )
670+ print (" Batch fix completed!" )
671+
672+ return true
673+ end
0 commit comments