Skip to content

Commit 15a064d

Browse files
committed
Final fix for self upgrade logic, working for editable installs as well.
1 parent 5349369 commit 15a064d

2 files changed

Lines changed: 108 additions & 29 deletions

File tree

omnipkg/core.py

Lines changed: 107 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -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\nName: {package_name}\nVersion: {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

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "omnipkg"
7-
version = "1.5.9"
7+
version = "1.5.8"
88
authors = [
99
{ name = "1minds3t", email = "1minds3t@proton.me" },
1010
]

0 commit comments

Comments
 (0)