#!/usr/bin/env python3 """ bootstrap CLI ~~~~~~~~~~~~~ Just call this file, and the magic happens ;) """ import hashlib import shlex import subprocess import sys import venv from pathlib import Path def print_no_pip_error(): print('Error: Pip not available!') print('Hint: "apt-get install python3-venv"\n') try: from ensurepip import version except ModuleNotFoundError as err: print(err) print('-' * 100) print_no_pip_error() raise else: if not version(): print_no_pip_error() sys.exit(-1) assert sys.version_info >= (3, 11), f'Python version {sys.version_info} is too old!' if sys.platform == 'win32': # wtf # Files under Windows, e.g.: .../.venv/Scripts/python.exe BIN_NAME = 'Scripts' FILE_EXT = '.exe' else: # Files under Linux/Mac and all other than Windows, e.g.: .../.venv/bin/python BIN_NAME = 'bin' FILE_EXT = '' BASE_PATH = Path(__file__).parent VENV_PATH = BASE_PATH / '.venv' BIN_PATH = VENV_PATH / BIN_NAME PYTHON_PATH = BIN_PATH / f'python3{FILE_EXT}' PIP_PATH = BIN_PATH / f'pip{FILE_EXT}' PIP_SYNC_PATH = BIN_PATH / f'pip-sync{FILE_EXT}' DEP_LOCK_PATH = BASE_PATH / 'requirements.dev.txt' DEP_HASH_PATH = VENV_PATH / '.dep_hash' # script file defined in pyproject.toml as [console_scripts] # (Under Windows: ".exe" not added!) PROJECT_SHELL_SCRIPT = BIN_PATH / 'djfritz_ynh_dev' def get_dep_hash(): """Get SHA512 hash from lock file content.""" return hashlib.sha512(DEP_LOCK_PATH.read_bytes()).hexdigest() def store_dep_hash(): """Generate .venv/.dep_hash""" DEP_HASH_PATH.write_text(get_dep_hash()) def venv_up2date(): """Is existing .venv is up-to-date?""" if DEP_HASH_PATH.is_file(): return DEP_HASH_PATH.read_text() == get_dep_hash() return False def verbose_check_call(*popen_args): print(f'\n+ {shlex.join(str(arg) for arg in popen_args)}\n') return subprocess.check_call(popen_args) def main(argv): assert DEP_LOCK_PATH.is_file(), f'File not found: "{DEP_LOCK_PATH}" !' # Create virtual env in ".venv/": if not PYTHON_PATH.is_file(): print(f'Create virtual env here: {VENV_PATH.absolute()}') builder = venv.EnvBuilder(symlinks=True, upgrade=True, with_pip=True) builder.create(env_dir=VENV_PATH) if not PROJECT_SHELL_SCRIPT.is_file() or not venv_up2date(): # Update pip verbose_check_call(PYTHON_PATH, '-m', 'pip', 'install', '-U', 'pip') # Install pip-tools verbose_check_call(PYTHON_PATH, '-m', 'pip', 'install', '-U', 'pip-tools') # install requirements via "pip-sync" verbose_check_call(PIP_SYNC_PATH, str(DEP_LOCK_PATH)) # install project verbose_check_call(PIP_PATH, 'install', '--no-deps', '-e', '.') store_dep_hash() # Call our entry point CLI: try: verbose_check_call(PROJECT_SHELL_SCRIPT, *argv[1:]) except subprocess.CalledProcessError as err: sys.exit(err.returncode) if __name__ == '__main__': main(sys.argv)