port_pyenv_setup.py 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. #! /usr/bin/env python3
  2. """
  3. Create symlinks to homebrewed python-installations that can be used alongside
  4. pyenv.
  5. After running this script, you can use e.g. homebrew's python@3.12 via
  6. ```
  7. pyenv shell 3.12
  8. ```
  9. - You can still install pyenv versions
  10. - you can still create virtualenvironments based on the homebrew versions
  11. """
  12. import subprocess
  13. from pathlib import Path
  14. from dataclasses import dataclass
  15. import logging
  16. import re
  17. logging.basicConfig(level=logging.DEBUG)
  18. @dataclass
  19. class Binary:
  20. executable: str
  21. links: list[str]
  22. BINARIES_TO_LINK = [
  23. Binary(executable="python{version}", links=["python3", "python"]),
  24. Binary(executable="pip{version}", links=["pip3", "pip"]),
  25. Binary(executable="idle{version}", links=["idle3", "idle"]),
  26. Binary(executable="pydoc{version}", links=["pydoc3", "pydoc"]),
  27. Binary(executable="wheel{version}", links=["wheel3", "wheel"]),
  28. Binary(
  29. executable="python{version}-config",
  30. links=["python3-config", "python-config"],
  31. ),
  32. ]
  33. DIRS_TO_LINK = ["include", "lib", "share"]
  34. logging.warning("Did not find homebrew, trying macports :P")
  35. brew_prefix = Path(subprocess.check_output(["which", "port"]).decode()).parent.parent / "Library/Frameworks/Python.framework/Versions"
  36. installed_brew_packages = ["".join(s.strip().split()[:2]) for s in subprocess.getoutput("port installed").split("\n")[1:]]
  37. installed_versions = [re.match(r"python(\d+)@.*", p).group(1) for p in installed_brew_packages if re.match(r"python(\d+)@.*", p)]
  38. installed_versions = [f"{v[0]}.{v[1:]}" for v in installed_versions]
  39. logging.debug(f"{brew_prefix=}")
  40. logging.debug(f"{installed_brew_packages=}")
  41. logging.debug(f"{installed_versions=}")
  42. logging.info(f"Will link following installed versions: {installed_versions}")
  43. homedir = Path.home()
  44. pyenv_dir = Path(subprocess.getoutput("pyenv root")) / "versions"
  45. logging.info(f"Will add all necessary links to {pyenv_dir}")
  46. for pyversion in installed_versions:
  47. logging.info(f"Check dir for {pyversion}")
  48. (pyenv_dir / pyversion).mkdir(exist_ok=True)
  49. for binary in BINARIES_TO_LINK:
  50. (pyenv_dir / pyversion / "bin").mkdir(exist_ok=True)
  51. target = (
  52. brew_prefix / pyversion / "bin" / binary.executable.format(version=pyversion)
  53. )
  54. source = pyenv_dir / pyversion / "bin" / binary.executable.format(version=pyversion)
  55. logging.debug(f"linking {source=} to {target=}")
  56. if source.is_symlink():
  57. logging.info(f"Unlinking existing link {source=}")
  58. source.unlink()
  59. source.symlink_to(target)
  60. for link in binary.links:
  61. source = pyenv_dir / pyversion / "bin" / link
  62. target = brew_prefix / pyversion / "bin" / binary.executable.format(version=pyversion)
  63. if source.is_symlink():
  64. logging.info(f"Unlinking existing {source=} -> {target=}.")
  65. source.unlink()
  66. logging.debug(f"Linking {source=} -> {target=}.")
  67. source.symlink_to(target)
  68. for dir_ in DIRS_TO_LINK:
  69. target = brew_prefix / pyversion / dir_
  70. source = pyenv_dir / pyversion / dir_
  71. if source.is_dir():
  72. if source.resolve() != target.resolve():
  73. logging.warning(
  74. f"{source} is a proper directory, skipping. "
  75. f"Linking for {pyversion} might be broken now."
  76. )
  77. continue
  78. if not source.is_symlink():
  79. source.symlink_to(target, target_is_directory=True)
  80. logging.info("Rehashing pyenv")
  81. subprocess.call(["pyenv", "rehash"])