diff --git a/python/ray/_private/runtime_env/uv.py b/python/ray/_private/runtime_env/uv.py index 3a934e3d69ad..345c37b6c30b 100644 --- a/python/ray/_private/runtime_env/uv.py +++ b/python/ray/_private/runtime_env/uv.py @@ -63,6 +63,8 @@ def __init__( logger.debug("Setting up uv for runtime_env: %s", runtime_env) self._target_dir = target_dir + # An empty directory is created to execute cmd. + self._exec_cwd = os.path.join(self._target_dir, "exec_cwd") self._runtime_env = runtime_env self._logger = logger @@ -91,6 +93,26 @@ async def _install_uv( logger.info("Installing package uv to %s", virtualenv_path) await check_output_cmd(uv_install_cmd, logger=logger, cwd=cwd, env=pip_env) + async def _check_uv_existence( + self, path: str, cwd: str, env: dict, logger: logging.Logger + ) -> bool: + """Check and return the existence of `uv` in virtual env.""" + python = virtualenv_utils.get_virtualenv_python(path) + + check_existence_cmd = [ + python, + "-m", + "uv", + "version", + ] + + try: + # If `uv` doesn't exist, exception will be thrown. + await check_output_cmd(check_existence_cmd, logger=logger, cwd=cwd, env=env) + return True + except Exception: + return False + async def _install_uv_packages( self, path: str, @@ -99,13 +121,18 @@ async def _install_uv_packages( pip_env: Dict, logger: logging.Logger, ): + """Install required python packages via `uv`.""" virtualenv_path = virtualenv_utils.get_virtualenv_path(path) python = virtualenv_utils.get_virtualenv_python(path) # TODO(fyrestone): Support -i, --no-deps, --no-cache-dir, ... requirements_file = dependency_utils.get_requirements_file(path, uv_packages) + # Check existence for `uv` and see if we could skip `uv` installation. + uv_exists = await self._check_uv_existence(python, cwd, pip_env, logger) + # Install uv, which acts as the default package manager. - await self._install_uv(path, cwd, pip_env, logger) + if not uv_exists: + await self._install_uv(path, cwd, pip_env, logger) # Avoid blocking the event loop. loop = get_running_loop() @@ -138,17 +165,18 @@ async def _run(self): # We create an empty directory for exec cmd so that the cmd will # run more stable. e.g. if cwd has ray, then checking ray will # look up ray in cwd instead of site packages. - exec_cwd = os.path.join(path, "exec_cwd") - os.makedirs(exec_cwd, exist_ok=True) + os.makedirs(self._exec_cwd, exist_ok=True) try: - await virtualenv_utils.create_or_get_virtualenv(path, exec_cwd, logger) + await virtualenv_utils.create_or_get_virtualenv( + path, self._exec_cwd, logger + ) python = virtualenv_utils.get_virtualenv_python(path) - async with dependency_utils.check_ray(python, exec_cwd, logger): + async with dependency_utils.check_ray(python, self._exec_cwd, logger): # Install packages with uv. await self._install_uv_packages( path, uv_packages, - exec_cwd, + self._exec_cwd, self._uv_env, logger, )