diff --git a/python/online/fxreader/pr34/commands_typed/cli_bootstrap.py b/python/online/fxreader/pr34/commands_typed/cli_bootstrap.py index b34ee7c..80189b9 100644 --- a/python/online/fxreader/pr34/commands_typed/cli_bootstrap.py +++ b/python/online/fxreader/pr34/commands_typed/cli_bootstrap.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import glob import io +import tempfile import dataclasses import pathlib import sys @@ -15,11 +16,13 @@ logger = logging.getLogger(__name__) @dataclasses.dataclass class PyProject: + path: pathlib.Path 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 + requirements: dict[str, pathlib.Path] = dataclasses.field(default_factory=lambda : dict()) def pyproject_load( d: pathlib.Path, @@ -49,6 +52,7 @@ def pyproject_load( res = PyProject( + path=d, dependencies=dependencies, ) @@ -88,12 +92,28 @@ def pyproject_load( for o in content['tool'][tool_name]['runtime_preload'] ] + if 'requirements' in content['tool'][tool_name]: + assert isinstance(content['tool'][tool_name]['requirements'], dict) + + res.requirements = { + k : d.parent / pathlib.Path(v) + # pathlib.Path(o) + for k, v in content['tool'][tool_name]['requirements'].items() + } + return res @dataclasses.dataclass class BootstrapSettings: env_path: pathlib.Path python_path: pathlib.Path + base_dir: pathlib.Path + uv_args: list[str] = dataclasses.field( + default_factory=lambda : os.environ.get( + 'UV_ARGS', + '--offline', + ).split(), + ) @classmethod def get( @@ -107,6 +127,7 @@ class BootstrapSettings: python_path = env_path / 'bin' / 'python3' return cls( + base_dir=base_dir, env_path=env_path, python_path=python_path, ) @@ -125,49 +146,26 @@ def env_bootstrap( for o in pip_find_links ], []) - subprocess.check_call([ - 'uv', 'venv', - *pip_find_links_args, - # '--seed', - '--offline', - str(bootstrap_settings.env_path) + features : list[str] = [] + + if pyproject.early_features: + features.extend(pyproject.early_features) + + requirements_name = '_'.join(sorted(features)) + + requirements_path : Optional[pathlib.Path] = None + + if requirements_name in pyproject.requirements: + requirements_path = pyproject.requirements[requirements_name] + else: + requirements_path = pyproject.path.parent / 'requirements.txt' + + requirements_in : list[str] = [] + + requirements_in.extend([ + 'uv', 'pip', 'build', 'setuptools', 'meson-python', 'pybind11' ]) - 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] @@ -178,17 +176,61 @@ def env_bootstrap( early_dependencies=early_dependencies, )) - if len(early_dependencies) > 0: + requirements_in.extend(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'), + # *bootstrap_settings.uv_args, + # *early_dependencies, + # ]) + + if not requirements_path.exists(): + with tempfile.NamedTemporaryFile( + mode='w', + prefix='requirements', + suffix='.in', + ) as f: + f.write( + '\n'.join(requirements_in) + ) + f.flush() + subprocess.check_call([ - bootstrap_settings.python_path, - '-m', - 'uv', 'pip', 'install', + 'uv', + 'pip', + 'compile', *pip_find_links_args, - # '-f', str(pathlib.Path(__file__).parent / 'deps' / 'dist'), - '--offline', - *early_dependencies, + '-p', + bootstrap_settings.python_path, + *bootstrap_settings.uv_args, + '-r', f.name, + '-o', str(requirements_path), ]) + subprocess.check_call([ + 'uv', 'venv', + *pip_find_links_args, + # '--seed', + *bootstrap_settings.uv_args, + str(bootstrap_settings.env_path) + ]) + + subprocess.check_call([ + 'uv', + 'pip', + 'install', + *pip_find_links_args, + '-p', + bootstrap_settings.python_path, + *bootstrap_settings.uv_args, + '-r', str(requirements_path), + ]) + + def paths_equal( a: pathlib.Path | str, b: pathlib.Path | str