#! /usr/bin/env python3 """ Create symlinks to homebrewed python-installations that can be used alongside pyenv. After running this script, you can use e.g. homebrew's python@3.12 via ``` pyenv shell 3.12 ``` - You can still install pyenv versions - you can still create virtualenvironments based on the homebrew versions """ import subprocess from pathlib import Path from dataclasses import dataclass import logging logging.basicConfig( level=logging.INFO, format="{%(pathname)s:%(lineno)d} %(levelname)s - %(message)s", ) @dataclass class Binary: executable: str links: list[str] BINARIES_TO_LINK = [ Binary(executable="python{version}", links=["python3", "python"]), Binary(executable="pip{version}", links=["pip3", "pip"]), Binary(executable="idle{version}", links=["idle3", "idle"]), Binary(executable="pydoc{version}", links=["pydoc3", "pydoc"]), Binary(executable="wheel{version}", links=["wheel3", "wheel"]), Binary( executable="python{version}-config", links=["python3-config", "python-config"], ), ] DIRS_TO_LINK = ["include", "lib", "share"] installed_brew_packages = subprocess.getoutput("brew list").split() brew_prefix = Path(subprocess.getoutput("brew --prefix")) / "opt" installed_versions = [ p for p in installed_brew_packages if "python@" in p or "python-tk@" in p ] logging.info(f"Will link following installed versions: {installed_versions}") homedir = Path.home() pyenv_dir = Path(subprocess.getoutput("pyenv root")) / "versions" logging.info(f"Will add all necessary links to {pyenv_dir}") for pyversion in installed_versions: logging.info(f"Check dir for {pyversion}") _, version = pyversion.split("@") if "-tk" in pyversion: link_dir = pyenv_dir / f"{version}-tk" else: link_dir = pyenv_dir / version link_dir.mkdir(exist_ok=True) for binary in BINARIES_TO_LINK: (link_dir / "bin").mkdir(exist_ok=True) target = ( brew_prefix / pyversion / "bin" / binary.executable.format(version=version) ) source = link_dir / "bin" / binary.executable.format(version=version) if source.is_file(): if source.resolve() != target.resolve(): logging.warning( f"{source} is different from {target}, skipping {pyversion}" ) continue source.symlink_to(target) for link in binary.links: source = link_dir / "bin" / link target = brew_prefix / pyversion / "libexec/bin" / link if source.is_symlink(): logging.warning(f"Link {target} -> {source} already exists. Skipping.") continue source.symlink_to(target) for dir_ in DIRS_TO_LINK: target = brew_prefix / pyversion / dir_ source = link_dir / dir_ if source.is_dir(): if source.resolve() != target.resolve(): logging.warning( f"{source} is a proper directory, skipping. " f"Linking for {pyversion} might be broken now." ) continue if not source.is_symlink(): source.symlink_to(target, target_is_directory=True) logging.info("Rehashing pyenv") subprocess.call(["pyenv", "rehash"])