#!/usr/bin/env python3 import glob import io import dataclasses import pathlib import sys import subprocess import os import logging import tomllib from typing import (Self, Optional, Any,) logger = logging.getLogger(__name__) @dataclasses.dataclass class PyProject: dependencies: dict[str, list[str]] early_features: Optional[list[str]] = None pip_find_links: Optional[list[pathlib.Path]] = None runtime_libdirs: Optional[list[pathlib.Path]] = None runtime_preload: Optional[list[pathlib.Path]] = None def pyproject_load( d: pathlib.Path, ) -> PyProject: with io.open(d, 'rb') as f: content = tomllib.load(f) assert isinstance(content, dict) dependencies : dict[str, list[str]] = dict() dependencies['default'] = content['project']['dependencies'] if ( 'optional-dependencies' in content['project'] ): assert isinstance( content['project']['optional-dependencies'], dict ) for k, v in content['project']['optional-dependencies'].items(): assert isinstance(v, list) assert isinstance(k, str) dependencies[k] = v res = PyProject( dependencies=dependencies, ) tool_name = 'online.fxreader.pr34'.replace('.', '-') if ( 'tool' in content and isinstance( content['tool'], dict ) and tool_name in content['tool'] and isinstance( content['tool'][tool_name], dict ) ): if 'early_features' in content['tool'][tool_name]: res.early_features = content['tool'][tool_name]['early_features'] if 'pip_find_links' in content['tool'][tool_name]: res.pip_find_links = [ d.parent / pathlib.Path(o) for o in content['tool'][tool_name]['pip_find_links'] ] if 'runtime_libdirs' in content['tool'][tool_name]: res.runtime_libdirs = [ d.parent / pathlib.Path(o) # pathlib.Path(o) for o in content['tool'][tool_name]['runtime_libdirs'] ] if 'runtime_preload' in content['tool'][tool_name]: res.runtime_preload = [ d.parent / pathlib.Path(o) # pathlib.Path(o) for o in content['tool'][tool_name]['runtime_preload'] ] return res @dataclasses.dataclass class BootstrapSettings: env_path: pathlib.Path python_path: pathlib.Path @classmethod def get( cls, base_dir: Optional[pathlib.Path] = None, ) -> Self: if base_dir is None: base_dir = pathlib.Path.cwd() env_path = base_dir / '.venv' python_path = env_path / 'bin' / 'python3' return cls( env_path=env_path, python_path=python_path, ) def env_bootstrap( bootstrap_settings: BootstrapSettings, pyproject: PyProject, ) -> None: pip_find_links : list[pathlib.Path] = [] if not pyproject.pip_find_links is None: pip_find_links.extend(pyproject.pip_find_links) pip_find_links_args = sum([ ['-f', str(o),] for o in pip_find_links ], []) subprocess.check_call([ 'uv', 'venv', *pip_find_links_args, # '--seed', '--offline', str(bootstrap_settings.env_path) ]) subprocess.check_call([ 'uv', 'pip', 'install', *pip_find_links_args, '-p', bootstrap_settings.python_path, '--offline', 'uv', 'pip', ]) subprocess.check_call([ bootstrap_settings.python_path, '-m', 'uv', 'pip', 'install', *pip_find_links_args, '--offline', 'build', 'setuptools', 'meson-python', 'pybind11', ]) # early_wheels = glob.glob( # str( # pathlib.Path(__file__).parent / 'deps' / 'dist' / 'early' / '*.whl' # ) # ) # if len(early_wheels) > 0: # subprocess.check_call([ # bootstrap_settings.python_path, # '-m', # 'uv', 'pip', 'install', # '--offline', # *early_wheels, # ]) if pyproject.early_features: early_dependencies = sum([ pyproject.dependencies[o] for o in pyproject.early_features ], []) logger.info(dict( early_dependencies=early_dependencies, )) if len(early_dependencies) > 0: subprocess.check_call([ bootstrap_settings.python_path, '-m', 'uv', 'pip', 'install', *pip_find_links_args, # '-f', str(pathlib.Path(__file__).parent / 'deps' / 'dist'), '--offline', *early_dependencies, ]) def paths_equal( a: pathlib.Path | str, b: pathlib.Path | str ) -> bool: return ( os.path.abspath(str(a)) == os.path.abspath(str(b)) ) def run( d: Optional[pathlib.Path] = None, cli_path: Optional[pathlib.Path] = None, ) -> None: if cli_path is None: cli_path = pathlib.Path(__file__).parent / 'cli.py' if d is None: d = pathlib.Path(__file__).parent / 'pyproject.toml' bootstrap_settings = BootstrapSettings.get() pyproject : PyProject = pyproject_load( d ) logging.basicConfig(level=logging.INFO) if not bootstrap_settings.env_path.exists(): env_bootstrap( bootstrap_settings=bootstrap_settings, pyproject=pyproject, ) logger.info([sys.executable, sys.argv, bootstrap_settings.python_path]) if not paths_equal(sys.executable, bootstrap_settings.python_path): os.execv( str(bootstrap_settings.python_path), [ str(bootstrap_settings.python_path), *sys.argv, ] ) os.execv( str(bootstrap_settings.python_path), [ str(bootstrap_settings.python_path), str( cli_path ), *sys.argv[1:], ] ) if __name__ == '__main__': run( d=pathlib.Path(__file__).parent / 'python' / 'pyproject.toml', cli_path=pathlib.Path(__file__).parent / 'python' / 'cli.py', )