@@ -1806,28 +1806,35 @@ def create_bubble_for_package(self, package_name: str, version: str, python_cont
18061806 install_source = None
18071807 is_editable = False
18081808
1809- try :
1810- # Check if this is a live, editable installation
1811- dist = importlib .metadata .distribution (package_name )
1812- direct_url_json = dist .read_text ('direct_url.json' )
1813- if direct_url_json :
1814- # direct_url.json exists for editable/local installs
1815- project_root = self .parent_omnipkg .config_manager ._find_project_root ()
1816- if project_root and (project_root / 'pyproject.toml' ).exists ():
1817- install_source = str (project_root )
1818- is_editable = True
1819- safe_print (f" - Detected local development source: { install_source } " )
1820- except (importlib .metadata .PackageNotFoundError , TypeError , FileNotFoundError ):
1821- pass
1809+ # --- CHECK IF THIS IS A DEV ENVIRONMENT (even if not currently installed) ---
1810+ if package_name == 'omnipkg' : # Special handling for omnipkg itself
1811+ project_root = self .parent_omnipkg .config_manager ._find_project_root ()
1812+ if project_root and (project_root / 'pyproject.toml' ).exists ():
1813+ # We're in a dev environment - use the source
1814+ install_source = str (project_root )
1815+ is_editable = True
1816+ safe_print (f" - Detected development environment: { install_source } " )
1817+
1818+ # --- FALLBACK: Check if currently installed version is editable ---
1819+ if not is_editable :
1820+ try :
1821+ dist = importlib .metadata .distribution (package_name )
1822+ direct_url_json = dist .read_text ('direct_url.json' )
1823+ if direct_url_json :
1824+ project_root = self .parent_omnipkg .config_manager ._find_project_root ()
1825+ if project_root and (project_root / 'pyproject.toml' ).exists ():
1826+ install_source = str (project_root )
1827+ is_editable = True
1828+ safe_print (f" - Detected local development source: { install_source } " )
1829+ except (importlib .metadata .PackageNotFoundError , TypeError , FileNotFoundError ):
1830+ pass
18221831
18231832 if not install_source :
18241833 install_source = f"{ package_name } =={ version } "
18251834 safe_print (f" - Using PyPI as source: { install_source } " )
18261835
18271836 # --- SPECIAL HANDLING FOR EDITABLE INSTALLS ---
18281837 if is_editable :
1829- # For editable installs, we can't do a temp pip install
1830- # Instead, copy directly from the source and current site-packages
18311838 return self ._create_bubble_from_editable_install (
18321839 package_name , version , install_source , python_context_version
18331840 )
@@ -1860,39 +1867,75 @@ def create_bubble_for_package(self, package_name: str, version: str, python_cont
18601867
18611868 return self ._create_deduplicated_bubble (installed_tree , bubble_path , temp_path , python_context_version = python_context_version )
18621869
1863-
18641870 def _create_bubble_from_editable_install (self , package_name : str , version : str ,
1865- source_path : str , python_context_version : str ) -> bool :
1871+ source_path : str , python_context_version : str ) -> bool :
18661872 """
18671873 Creates a bubble from an editable install by copying files from the live source.
1868- This preserves the current dev version as a fallback before upgrading .
1874+ This preserves the current dev version WITH ALL DEPENDENCIES as a fallback.
18691875 """
18701876 safe_print (f" - Creating bubble from editable source (copy, not move)..." )
18711877
18721878 try :
18731879 source_root = Path (source_path )
18741880
18751881 # Find the actual package directory in the source
1876- # Could be: /home/minds3t/omnipkg/omnipkg (the package itself)
18771882 package_dir = source_root / package_name
18781883 if not package_dir .exists ():
1879- # Sometimes the package is at the root level
18801884 package_dir = source_root
18811885
1882- # Get current site-packages to find dependencies
1883- site_packages = Path (self .parent_omnipkg .config ['site_packages' ])
1886+ # Get current site-packages
1887+ site_packages = None
1888+ for key in ['site_packages' , 'main_site_packages' , 'site_packages_path' ]:
1889+ if key in self .parent_omnipkg .config :
1890+ site_packages = Path (self .parent_omnipkg .config [key ])
1891+ break
1892+
1893+ if not site_packages :
1894+ import site as site_module
1895+ site_packages = Path (site_module .getsitepackages ()[0 ])
18841896
1885- # Create a temp directory with the package files
1897+ safe_print (f" - Using site-packages: { site_packages } " )
1898+
1899+ # --- GET DEPENDENCIES FROM PYPROJECT.TOML ---
1900+ dependencies = []
1901+ pyproject_path = source_root / 'pyproject.toml'
1902+ if pyproject_path .exists ():
1903+ try :
1904+ if sys .version_info >= (3 , 11 ):
1905+ import tomllib
1906+ else :
1907+ import tomli as tomllib
1908+
1909+ with open (pyproject_path , 'rb' ) as f :
1910+ pyproject_data = tomllib .load (f )
1911+
1912+ # Get dependencies from project.dependencies
1913+ deps = pyproject_data .get ('project' , {}).get ('dependencies' , [])
1914+
1915+ # Parse dependency specs (e.g., "requests>=2.20" -> "requests")
1916+ for dep_spec in deps :
1917+ # Remove version constraints, extras, and markers
1918+ dep_name = dep_spec .split ('[' )[0 ].split ('>' )[0 ].split ('<' )[0 ].split ('=' )[0 ].split (';' )[0 ].strip ()
1919+ if dep_name :
1920+ dependencies .append (dep_name )
1921+
1922+ safe_print (f" - Found { len (dependencies )} dependencies in pyproject.toml" )
1923+ except Exception as e :
1924+ safe_print (f" - ⚠️ Could not parse pyproject.toml: { e } " )
1925+
1926+ # Create a temp directory with the package AND its dependencies
18861927 with tempfile .TemporaryDirectory () as temp_dir :
18871928 temp_path = Path (temp_dir )
1888- temp_pkg_dir = temp_path / package_name
18891929
1890- # Copy the package source to temp
1930+ # 1. Copy the main package source
1931+ temp_pkg_dir = temp_path / package_name
18911932 safe_print (f" - Copying source files from: { package_dir } " )
18921933 shutil .copytree (package_dir , temp_pkg_dir , symlinks = False ,
1893- ignore = shutil .ignore_patterns ('__pycache__' , '*.pyc' , '.git*' , '.pytest_cache' ))
1934+ ignore = shutil .ignore_patterns ('__pycache__' , '*.pyc' , '.git*' ,
1935+ '.pytest_cache' , '*.egg-info' ))
18941936
1895- # Copy the dist-info or egg-info from site-packages
1937+ # 2. Copy the package's dist-info
1938+ dist_info_found = False
18961939 for dist_info_pattern in [f'{ package_name } -*.dist-info' ,
18971940 f'{ package_name } -*.egg-info' ,
18981941 f'__editable__*.dist-info' ]:
@@ -1904,8 +1947,44 @@ def _create_bubble_from_editable_install(self, package_name: str, version: str,
19041947 else :
19051948 shutil .copy2 (dist_info , dest )
19061949 safe_print (f" - Copied metadata: { dist_info .name } " )
1950+ dist_info_found = True
1951+
1952+ if not dist_info_found :
1953+ safe_print (f" - ⚠️ No dist-info found, creating minimal metadata..." )
1954+ dist_info_dir = temp_path / f'{ package_name } -{ version } .dist-info'
1955+ dist_info_dir .mkdir (exist_ok = True )
1956+ metadata_file = dist_info_dir / 'METADATA'
1957+ metadata_file .write_text (f"Metadata-Version: 2.1\n Name: { package_name } \n Version: { version } \n " )
1958+
1959+ # 3. Copy ALL dependencies from site-packages
1960+ safe_print (f" - Copying { len (dependencies )} dependencies..." )
1961+ for dep_name in dependencies :
1962+ dep_canonical = canonicalize_name (dep_name )
1963+
1964+ # Find and copy the dependency package directory
1965+ dep_dir = None
1966+ for potential_name in [dep_name , dep_name .replace ('-' , '_' ), dep_canonical ]:
1967+ potential_dir = site_packages / potential_name
1968+ if potential_dir .exists () and potential_dir .is_dir ():
1969+ dep_dir = potential_dir
1970+ break
1971+
1972+ if dep_dir :
1973+ dest_dir = temp_path / dep_dir .name
1974+ if not dest_dir .exists ():
1975+ shutil .copytree (dep_dir , dest_dir , symlinks = False ,
1976+ ignore = shutil .ignore_patterns ('__pycache__' , '*.pyc' ))
1977+ safe_print (f" ✓ { dep_name } " )
1978+
1979+ # Copy dependency's dist-info
1980+ for dist_info in site_packages .glob (f'{ dep_canonical } *.dist-info' ):
1981+ dest = temp_path / dist_info .name
1982+ if not dest .exists ():
1983+ shutil .copytree (dist_info , dest , symlinks = False )
1984+
1985+ safe_print (f" - Analyzing complete dependency tree..." )
19071986
1908- # Now analyze what we copied
1987+ # Now analyze what we copied (package + all deps)
19091988 installed_tree = self ._analyze_installed_tree (temp_path )
19101989
19111990 # Create the final bubble
0 commit comments