@@ -5803,7 +5803,7 @@ def check_package_installed_fast(self, python_exe: str, package: str, version: s
58035803 duration_ms = (time .perf_counter () - start_time ) * 1000
58045804 return False , duration_ms
58055805
5806- def smart_install (self , packages : List [str ], dry_run : bool = False , force_reinstall : bool = False , target_directory : Optional [Path ] = None ) -> int :
5806+ def smart_install (self , packages : List [str ], dry_run : bool = False , force_reinstall : bool = False , override_strategy : Optional [ str ] = None , target_directory : Optional [Path ] = None ) -> int :
58075807 # ====================================================================
58085808 # ULTRA-FAST PREFLIGHT CHECK (Before any heavy initialization)
58095809 # ====================================================================
@@ -5865,6 +5865,12 @@ def smart_install(self, packages: List[str], dry_run: bool = False, force_reinst
58655865 # ====================================================================
58665866 # NORMAL INITIALIZATION (Only runs if packages need work)
58675867 # ====================================================================
5868+ original_strategy = None
5869+ if override_strategy :
5870+ original_strategy = self .config .get ('install_strategy' , 'stable-main' )
5871+ if original_strategy != override_strategy :
5872+ safe_print (f' - 🔄 Using override strategy: { override_strategy } ' )
5873+ self .config ['install_strategy' ] = override_strategy
58685874 if not self ._connect_cache ():
58695875 return 1
58705876 self ._heal_conda_environment ()
@@ -5961,13 +5967,19 @@ def smart_install(self, packages: List[str], dry_run: bool = False, force_reinst
59615967 try :
59625968 for pkg_spec in needs_resolution :
59635969 safe_print (f' 🔍 Resolving version for { pkg_spec } ...' )
5964- resolved = self ._resolve_package_versions ([pkg_spec ])
5965- if not resolved :
5970+ try : # ADD THIS INNER TRY-CATCH
5971+ resolved = self ._resolve_package_versions ([pkg_spec ])
5972+ if not resolved :
5973+ all_packages_satisfied = False
5974+ break
5975+ except ValueError as e : # CATCH THE ValueError HERE
5976+ safe_print (f"❌ Failed to resolve '{ pkg_spec } ': { e } " )
59665977 all_packages_satisfied = False
59675978 break
5979+
59685980 resolved_spec = resolved [0 ]
59695981 resolved_specs .append (resolved_spec )
5970- resolved_package_cache [pkg_spec ] = resolved_spec # Cache the resolution result
5982+ resolved_package_cache [pkg_spec ] = resolved_spec
59715983
59725984 # Now check if this resolved version is satisfied via fast check
59735985 pkg_name , version = self ._parse_package_spec (resolved_spec )
@@ -5999,6 +6011,9 @@ def smart_install(self, packages: List[str], dry_run: bool = False, force_reinst
59996011 new_omnipkg_instance = self .__class__ (new_config_manager )
60006012
60016013 return new_omnipkg_instance .smart_install (packages , dry_run , force_reinstall , target_directory )
6014+ if not all_packages_satisfied :
6015+ safe_print (_ ('❌ Could not resolve all packages. Aborting installation.' ))
6016+ return 1
60026017
60036018 # Phase 3: KB check only for complex cases (nested packages, complex strategies)
60046019 if needs_kb_check and all_packages_satisfied :
@@ -6159,13 +6174,18 @@ def smart_install(self, packages: List[str], dry_run: bool = False, force_reinst
61596174 else :
61606175 # Force reinstall case or no cache - resolve normally with full logging
61616176 resolved_packages = self ._resolve_package_versions (packages_to_process )
6162-
6177+
6178+
61636179 if not resolved_packages :
61646180 safe_print (_ ('❌ Could not resolve any packages to install. Aborting.' ))
61656181 return 1
61666182
61676183 sorted_packages = self ._sort_packages_for_install (resolved_packages , strategy = install_strategy )
61686184
6185+ except ValueError as e : # ADD THIS CATCH BLOCK
6186+ safe_print (f"\n ❌ Resolution failed: { e } " )
6187+ return 1
6188+
61696189 except NoCompatiblePythonError as e :
61706190 # --- THIS IS THE "QUANTUM HEALING" CATCH BLOCK ---
61716191 safe_print ("\n " + "=" * 60 )
@@ -6201,9 +6221,10 @@ def smart_install(self, packages: List[str], dry_run: bool = False, force_reinst
62016221
62026222 # Rest of the installation logic remains the same...
62036223 user_requested_cnames = {canonicalize_name (self ._parse_package_spec (p )[0 ]) for p in packages }
6204- any_installations_made = False
62056224 main_env_kb_updates = {}
6225+ any_failures = False
62066226 bubbled_kb_updates = {}
6227+ any_installations_made = False
62076228 kb_deletions = set ()
62086229
62096230 for package_spec in sorted_packages :
@@ -6259,6 +6280,7 @@ def smart_install(self, packages: List[str], dry_run: bool = False, force_reinst
62596280
62606281 if return_code != 0 :
62616282 safe_print ('❌ Unrecoverable installation failure for {}. Continuing...' .format (package_spec ))
6283+ any_failures = True # <--- ADD THIS LINE
62626284 continue
62636285
62646286 any_installations_made = True
@@ -6365,11 +6387,11 @@ def smart_install(self, packages: List[str], dry_run: bool = False, force_reinst
63656387
63666388 # Re-run the entire smart_install with the original package list
63676389 return new_omnipkg_instance .smart_install (packages , dry_run , force_reinstall , target_directory )
6368-
6369- if not any_installations_made :
6370- safe_print (_ ('\n ✅ All requirements were already satisfied.' ))
6371- return 0
63726390
6391+ except ValueError as e :
6392+ safe_print (f"\n ❌ Aborting installation: { e } " )
6393+ return 1
6394+
63736395 # Knowledge base update and cleanup logic remains the same...
63746396 safe_print (_ ('\n 🧠 Updating knowledge base (consolidated)...' ))
63756397 all_changed_specs = set ()
@@ -6404,21 +6426,7 @@ def smart_install(self, packages: List[str], dry_run: bool = False, force_reinst
64046426
64056427 # Cleanup and final steps
64066428 if not force_reinstall :
6407- safe_print (_ ('\n 🧹 Cleaning redundant bubbles...' ))
6408- final_active_packages = self .get_installed_packages (live = True )
6409- cleaned_count = 0
6410- for pkg_name , active_version in final_active_packages .items ():
6411- bubble_path = self .multiversion_base / f'{ pkg_name } -{ active_version } '
6412- if bubble_path .exists () and bubble_path .is_dir ():
6413- try :
6414- shutil .rmtree (bubble_path )
6415- cleaned_count += 1
6416- if hasattr (self , 'hook_manager' ):
6417- self .hook_manager .remove_bubble_from_tracking (pkg_name , active_version )
6418- except Exception as e :
6419- safe_print (_ (' ❌ Failed to remove bubble directory: {}' ).format (e ))
6420- if cleaned_count > 0 :
6421- safe_print (' ✅ Removed {} redundant bubbles' .format (cleaned_count ))
6429+ self ._cleanup_redundant_bubbles ()
64226430
64236431 safe_print (_ ('\n 🎉 All package operations complete.' ))
64246432 self ._save_last_known_good_snapshot ()
@@ -6665,6 +6673,37 @@ def _find_compatible_python_version(self, package_name: str, target_package_vers
66656673 traceback .print_exc ()
66666674 return None
66676675
6676+ def _cleanup_redundant_bubbles (self ):
6677+ """
6678+ Scans for and removes any bubbles that are identical to the currently
6679+ active version of a package in the main environment.
6680+ """
6681+ safe_print (_ ('\n 🧹 Cleaning redundant bubbles...' ))
6682+ try :
6683+ final_active_packages = self .get_installed_packages (live = True )
6684+ cleaned_count = 0
6685+ for pkg_name , active_version in final_active_packages .items ():
6686+ # Construct the path to a potentially redundant bubble
6687+ bubble_path = self .multiversion_base / f'{ pkg_name } -{ active_version } '
6688+
6689+ if bubble_path .exists () and bubble_path .is_dir ():
6690+ safe_print (f" - Found redundant bubble for active package: { pkg_name } =={ active_version } " )
6691+ try :
6692+ shutil .rmtree (bubble_path )
6693+ cleaned_count += 1
6694+ if hasattr (self , 'hook_manager' ):
6695+ self .hook_manager .remove_bubble_from_tracking (pkg_name , active_version )
6696+ safe_print (f" - ✅ Removed redundant bubble." )
6697+ except Exception as e :
6698+ safe_print (_ (' ❌ Failed to remove bubble directory: {}' ).format (e ))
6699+
6700+ if cleaned_count > 0 :
6701+ safe_print (' ✅ Removed {} redundant bubble(s).' .format (cleaned_count ))
6702+ else :
6703+ safe_print (' ✅ No redundant bubbles found.' )
6704+ except Exception as e :
6705+ safe_print (f" - ⚠️ An error occurred during bubble cleanup: { e } " )
6706+
66686707 def _detect_conda_corruption_from_error (self , stderr_output : str ) -> Optional [Tuple [str , str ]]:
66696708 """
66706709 Detect corruption patterns in conda command stderr output.
@@ -7717,7 +7756,9 @@ def _resolve_package_versions(self, packages: List[str]) -> List[str]:
77177756 safe_print (_ (" ✅ Resolved '{}' to '{}'" ).format (pkg_name , new_spec ))
77187757 resolved_packages .append (new_spec )
77197758 else :
7720- safe_print (_ (" ⚠️ Could not resolve a version for '{}' via PyPI. Skipping." ).format (pkg_name ))
7759+ safe_print (_ (" ❌ CRITICAL: Could not resolve a version for '{}' via PyPI." ).format (pkg_name ))
7760+ # Raise an exception to abort the entire installation
7761+ raise ValueError (f"Package '{ pkg_name } ' not found or could not be resolved." )
77217762
77227763 return resolved_packages
77237764
0 commit comments