Files
ray-project--ray/python/ray/setup-dev.py
T
2026-07-13 13:17:40 +08:00

203 lines
7.0 KiB
Python
Executable File

#!/usr/bin/env python
# ruff: noqa: E402
"""This script allows you to develop Ray Python code without needing to compile
Ray.
See https://docs.ray.io/en/master/development.html#building-ray-python-only"""
import os
import sys
# types.py can conflict with stdlib's types.py in some python versions,
# see https://github.com/python/cpython/issues/101210.
# To avoid import errors, we move the current working dir to the end of sys.path.
this_dir = os.path.dirname(__file__)
if this_dir in sys.path:
sys.path.remove(this_dir)
sys.path.append(this_dir)
import argparse
import shutil
import subprocess
import click
import ray
def do_link(package, force=False, skip_list=None, allow_list=None, local_path=None):
if skip_list and package in skip_list:
print(f"Skip creating symbolic link for {package}")
return
if allow_list is not None and package not in allow_list:
print(f"Skip creating symbolic link for {package} (not in allow list)")
return
package_home = os.path.abspath(os.path.join(ray.__file__, f"../{package}"))
# Infer local_path automatically.
if local_path is None:
local_path = f"../{package}"
local_home = os.path.abspath(os.path.join(__file__, local_path))
# If installed package dir does not exist, continue either way. We'll
# remove it/create a link from there anyways.
if not os.path.isdir(package_home) and not os.path.isfile(package_home):
print(f"{package_home} does not exist. Continuing to link.")
# Make sure the path we are linking to does exist.
assert os.path.exists(local_home), local_home
# Confirm with user.
if not force and not click.confirm(
f"This will replace:\n {package_home}\nwith " f"a symlink to:\n {local_home}",
default=True,
):
return
# Windows: Create directory junction.
if os.name == "nt":
try:
shutil.rmtree(package_home)
except FileNotFoundError:
pass
except OSError:
os.remove(package_home)
# create symlink for directory or file
if os.path.isdir(local_home):
subprocess.check_call(
["mklink", "/J", package_home, local_home], shell=True
)
elif os.path.isfile(local_home):
subprocess.check_call(
["mklink", "/H", package_home, local_home], shell=True
)
else:
print(f"{local_home} is neither directory nor file. Link failed.")
# Posix: Use `ln -s` to create softlink.
else:
if os.path.exists(package_home) and os.path.realpath(
package_home
) == os.path.realpath(local_home):
print(f"{package} is already linked to source. Skipping.")
return
sudo = []
if not os.access(os.path.dirname(package_home), os.W_OK):
print("You don't have write permission " f"to {package_home}, using sudo:")
sudo = ["sudo"]
print(f"Creating symbolic link from \n {local_home} to \n {package_home}")
# Preserve ray/serve/generated
serve_temp_dir = "/tmp/ray/_serve/"
if package == "serve":
# Copy generated folder to a temp dir
generated_folder = os.path.join(package_home, "generated")
if os.path.exists(serve_temp_dir):
subprocess.check_call(sudo + ["rm", "-rf", serve_temp_dir])
subprocess.check_call(sudo + ["mkdir", "-p", serve_temp_dir])
subprocess.check_call(sudo + ["mv", generated_folder, serve_temp_dir])
# Create backup of the old directory if it exists
if os.path.exists(package_home):
backup_dir = f"{package_home}.bak"
print(f"Creating backup of {package_home} to {backup_dir}")
subprocess.check_call(sudo + ["cp", "-r", package_home, backup_dir])
subprocess.check_call(sudo + ["rm", "-rf", package_home])
subprocess.check_call(sudo + ["ln", "-s", local_home, package_home])
# Move generated folder to local_home
if package == "serve":
tmp_generated_folder = os.path.join(serve_temp_dir, "generated")
package_generated_folder = os.path.join(package_home, "generated")
if not os.path.exists(package_generated_folder):
subprocess.check_call(
["mv", tmp_generated_folder, package_generated_folder]
)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter, description="Setup dev."
)
parser.add_argument(
"--yes", "-y", action="store_true", help="Don't ask for confirmation."
)
parser.add_argument(
"--skip",
"-s",
nargs="*",
help="List of folders to skip linking to facilitate workspace dev",
required=False,
)
parser.add_argument(
"--allow",
"-a",
nargs="*",
help="List of folders to link (only these will be linked)",
required=False,
)
parser.add_argument(
"--extras",
"-e",
nargs="*",
help="List of extra folders to link to facilitate workspace dev",
required=False,
)
args = parser.parse_args()
if args.skip and args.allow:
print("Error: --skip and --allow cannot be used together.")
sys.exit(1)
if not args.yes:
print("NOTE: Use '-y' to override all python files without confirmation.")
# Dictionary of packages to link, with optional local_path
packages_to_link = {
"llm": None,
"serve/llm": None,
"data/llm.py": None,
"rllib": "../../../rllib",
"air": None,
"tune": None,
"train": None,
"autoscaler": None,
"cloudpickle": None,
"data": None,
"scripts": None,
"internal": None,
"tests": None,
"experimental": None,
"util": None,
"workflow": None,
"serve": None,
"dag": None,
"widgets": None,
"cluster_utils.py": None,
"_private": None,
"_common": None,
"dashboard": None,
}
# Link all packages using a for loop
for package, local_path in packages_to_link.items():
do_link(
package,
force=args.yes,
skip_list=args.skip,
allow_list=args.allow,
local_path=local_path,
)
if args.extras is not None:
for package in args.extras:
do_link(package, force=args.yes, skip_list=args.skip, allow_list=args.allow)
print(
"Created links.\n\nIf you run into issues initializing Ray, please "
"ensure that your local repo and the installed Ray are in sync "
"(pip install -U the latest wheels at "
"https://docs.ray.io/en/master/installation.html, "
"and ensure you are up-to-date on the master branch on git).\n\n"
"Note that you may need to delete the package symlinks when pip "
"installing new Ray versions to prevent pip from overwriting files "
"in your git repo."
)