fast_editable.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. #!/usr/bin/env python3
  2. from __future__ import annotations
  3. import argparse
  4. import configparser
  5. import os.path
  6. import stat
  7. import sys
  8. import sysconfig
  9. def main() -> int:
  10. parser = argparse.ArgumentParser()
  11. parser.add_argument("--path", default=".")
  12. args = parser.parse_args()
  13. # simulate `pip install -e .` -- but bypass setuptools
  14. def r(p: str) -> str:
  15. return os.path.relpath(p, ".")
  16. # must be in a virtualenv
  17. assert not sys.flags.no_site, sys.flags.no_site
  18. site_dir = sysconfig.get_path("purelib")
  19. bin_dir = sysconfig.get_path("scripts")
  20. assert "/.venv/" in site_dir, site_dir
  21. cfg = configparser.RawConfigParser()
  22. cfg.read(os.path.join(args.path, "setup.cfg"))
  23. package_name = cfg["metadata"]["name"]
  24. package_dir = cfg["options"].get("package_dir", "").strip()
  25. if package_dir not in {"", "=src"}:
  26. raise SystemExit(f"unsupported package_dir={package_dir!r}")
  27. project_root = os.path.abspath(args.path)
  28. if package_dir == "=src":
  29. source_root = os.path.join(project_root, "src")
  30. project_root_relative = "../"
  31. else:
  32. source_root = project_root
  33. project_root_relative = "."
  34. # egg-link indicates that the software is installed
  35. egg_link = os.path.join(site_dir, f"{package_name}.egg-link")
  36. print(f"writing {r(egg_link)}...")
  37. with open(egg_link, "w") as f:
  38. f.write(f"{source_root}\n{project_root_relative}")
  39. # easy-install.pth is how code gets imported
  40. easy_install_pth = os.path.join(site_dir, "easy-install.pth")
  41. print(f"adding {r(source_root)} to {r(easy_install_pth)}...")
  42. try:
  43. with open(easy_install_pth) as f:
  44. easy_install_paths = f.read().splitlines()
  45. except OSError:
  46. easy_install_paths = []
  47. if source_root not in easy_install_paths:
  48. easy_install_paths.append(source_root)
  49. with open(easy_install_pth, "w") as f:
  50. f.write("\n".join(easy_install_paths) + "\n")
  51. # 0. create bin scripts for anything in `console_scripts`
  52. console_scripts = cfg["options.entry_points"]["console_scripts"].strip()
  53. for line in console_scripts.splitlines():
  54. entry, rest = line.split(" = ")
  55. mod, attr = rest.split(":")
  56. binary = os.path.join(bin_dir, entry)
  57. print(f"writing {r(binary)}...")
  58. with open(binary, "w") as f:
  59. f.write(
  60. f"#!{sys.executable}\n"
  61. f"from {mod} import {attr}\n"
  62. f'if __name__ == "__main__":\n'
  63. f" raise SystemExit({attr}())\n"
  64. )
  65. mode = os.stat(binary).st_mode
  66. mode |= stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
  67. os.chmod(binary, mode)
  68. # 0. write out the `sentry.egg-info` directory in `src/`
  69. egg_info_dir = os.path.join(source_root, f"{package_name}.egg-info")
  70. print(f"creating {r(egg_info_dir)}...")
  71. os.makedirs(egg_info_dir, exist_ok=True)
  72. entry_points_txt = os.path.join(egg_info_dir, "entry_points.txt")
  73. print(f"writing {r(entry_points_txt)}...")
  74. ep_cfg = configparser.RawConfigParser()
  75. for section, eps in cfg["options.entry_points"].items():
  76. ep_cfg.add_section(section)
  77. for ep in eps.strip().splitlines():
  78. k, v = ep.split(" = ")
  79. ep_cfg[section][k] = v
  80. with open(entry_points_txt, "w") as f:
  81. ep_cfg.write(f)
  82. pkg_info = os.path.join(egg_info_dir, "PKG-INFO")
  83. print(f"writing {r(pkg_info)}...")
  84. with open(pkg_info, "w") as f:
  85. f.write(
  86. f"Metadata-Version: 2.1\n"
  87. f"Name: {package_name}\n"
  88. f"Version: {cfg['metadata']['version']}\n"
  89. )
  90. return 0
  91. if __name__ == "__main__":
  92. raise SystemExit(main())