setup-dev.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. #!/usr/bin/env python
  2. # ruff: noqa: E402
  3. """This script allows you to develop Ray Python code without needing to compile
  4. Ray.
  5. See https://docs.ray.io/en/master/development.html#building-ray-python-only"""
  6. import os
  7. import sys
  8. # types.py can conflict with stdlib's types.py in some python versions,
  9. # see https://github.com/python/cpython/issues/101210.
  10. # To avoid import errors, we move the current working dir to the end of sys.path.
  11. this_dir = os.path.dirname(__file__)
  12. if this_dir in sys.path:
  13. sys.path.remove(this_dir)
  14. sys.path.append(this_dir)
  15. import argparse
  16. import shutil
  17. import subprocess
  18. import click
  19. import ray
  20. def do_link(package, force=False, skip_list=None, allow_list=None, local_path=None):
  21. if skip_list and package in skip_list:
  22. print(f"Skip creating symbolic link for {package}")
  23. return
  24. if allow_list is not None and package not in allow_list:
  25. print(f"Skip creating symbolic link for {package} (not in allow list)")
  26. return
  27. package_home = os.path.abspath(os.path.join(ray.__file__, f"../{package}"))
  28. # Infer local_path automatically.
  29. if local_path is None:
  30. local_path = f"../{package}"
  31. local_home = os.path.abspath(os.path.join(__file__, local_path))
  32. # If installed package dir does not exist, continue either way. We'll
  33. # remove it/create a link from there anyways.
  34. if not os.path.isdir(package_home) and not os.path.isfile(package_home):
  35. print(f"{package_home} does not exist. Continuing to link.")
  36. # Make sure the path we are linking to does exist.
  37. assert os.path.exists(local_home), local_home
  38. # Confirm with user.
  39. if not force and not click.confirm(
  40. f"This will replace:\n {package_home}\nwith " f"a symlink to:\n {local_home}",
  41. default=True,
  42. ):
  43. return
  44. # Windows: Create directory junction.
  45. if os.name == "nt":
  46. try:
  47. shutil.rmtree(package_home)
  48. except FileNotFoundError:
  49. pass
  50. except OSError:
  51. os.remove(package_home)
  52. # create symlink for directory or file
  53. if os.path.isdir(local_home):
  54. subprocess.check_call(
  55. ["mklink", "/J", package_home, local_home], shell=True
  56. )
  57. elif os.path.isfile(local_home):
  58. subprocess.check_call(
  59. ["mklink", "/H", package_home, local_home], shell=True
  60. )
  61. else:
  62. print(f"{local_home} is neither directory nor file. Link failed.")
  63. # Posix: Use `ln -s` to create softlink.
  64. else:
  65. sudo = []
  66. if not os.access(os.path.dirname(package_home), os.W_OK):
  67. print("You don't have write permission " f"to {package_home}, using sudo:")
  68. sudo = ["sudo"]
  69. print(f"Creating symbolic link from \n {local_home} to \n {package_home}")
  70. # Preserve ray/serve/generated
  71. serve_temp_dir = "/tmp/ray/_serve/"
  72. if package == "serve":
  73. # Copy generated folder to a temp dir
  74. generated_folder = os.path.join(package_home, "generated")
  75. if not os.path.exists(serve_temp_dir):
  76. os.makedirs(serve_temp_dir)
  77. subprocess.check_call(["mv", generated_folder, serve_temp_dir])
  78. # Create backup of the old directory if it exists
  79. if os.path.exists(package_home):
  80. backup_dir = f"{package_home}.bak"
  81. print(f"Creating backup of {package_home} to {backup_dir}")
  82. subprocess.check_call(sudo + ["cp", "-r", package_home, backup_dir])
  83. subprocess.check_call(sudo + ["rm", "-rf", package_home])
  84. subprocess.check_call(sudo + ["ln", "-s", local_home, package_home])
  85. # Move generated folder to local_home
  86. if package == "serve":
  87. tmp_generated_folder = os.path.join(serve_temp_dir, "generated")
  88. package_generated_folder = os.path.join(package_home, "generated")
  89. if not os.path.exists(package_generated_folder):
  90. subprocess.check_call(
  91. ["mv", tmp_generated_folder, package_generated_folder]
  92. )
  93. if __name__ == "__main__":
  94. parser = argparse.ArgumentParser(
  95. formatter_class=argparse.RawDescriptionHelpFormatter, description="Setup dev."
  96. )
  97. parser.add_argument(
  98. "--yes", "-y", action="store_true", help="Don't ask for confirmation."
  99. )
  100. parser.add_argument(
  101. "--skip",
  102. "-s",
  103. nargs="*",
  104. help="List of folders to skip linking to facilitate workspace dev",
  105. required=False,
  106. )
  107. parser.add_argument(
  108. "--allow",
  109. "-a",
  110. nargs="*",
  111. help="List of folders to link (only these will be linked)",
  112. required=False,
  113. )
  114. parser.add_argument(
  115. "--extras",
  116. "-e",
  117. nargs="*",
  118. help="List of extra folders to link to facilitate workspace dev",
  119. required=False,
  120. )
  121. args = parser.parse_args()
  122. if args.skip and args.allow:
  123. print("Error: --skip and --allow cannot be used together.")
  124. sys.exit(1)
  125. if not args.yes:
  126. print("NOTE: Use '-y' to override all python files without confirmation.")
  127. # Dictionary of packages to link, with optional local_path
  128. packages_to_link = {
  129. "llm": None,
  130. "serve/llm": None,
  131. "data/llm.py": None,
  132. "rllib": "../../../rllib",
  133. "air": None,
  134. "tune": None,
  135. "train": None,
  136. "autoscaler": None,
  137. "cloudpickle": None,
  138. "data": None,
  139. "scripts": None,
  140. "internal": None,
  141. "tests": None,
  142. "experimental": None,
  143. "util": None,
  144. "workflow": None,
  145. "serve": None,
  146. "dag": None,
  147. "widgets": None,
  148. "cluster_utils.py": None,
  149. "_private": None,
  150. "_common": None,
  151. "dashboard": None,
  152. }
  153. # Link all packages using a for loop
  154. for package, local_path in packages_to_link.items():
  155. do_link(
  156. package,
  157. force=args.yes,
  158. skip_list=args.skip,
  159. allow_list=args.allow,
  160. local_path=local_path,
  161. )
  162. if args.extras is not None:
  163. for package in args.extras:
  164. do_link(package, force=args.yes, skip_list=args.skip, allow_list=args.allow)
  165. print(
  166. "Created links.\n\nIf you run into issues initializing Ray, please "
  167. "ensure that your local repo and the installed Ray are in sync "
  168. "(pip install -U the latest wheels at "
  169. "https://docs.ray.io/en/master/installation.html, "
  170. "and ensure you are up-to-date on the master branch on git).\n\n"
  171. "Note that you may need to delete the package symlinks when pip "
  172. "installing new Ray versions to prevent pip from overwriting files "
  173. "in your git repo."
  174. )