|
14 | 14 | import asyncio |
15 | 15 | import json |
16 | 16 | import shlex |
| 17 | +import subprocess |
17 | 18 | import tomllib |
18 | 19 | from glob import glob |
19 | 20 | from os import environ, makedirs |
|
27 | 28 | import rich |
28 | 29 | import uvicorn |
29 | 30 | from devtools import pprint |
30 | | -from omegaconf import DictConfig, OmegaConf |
| 31 | +from omegaconf import DictConfig, OmegaConf, open_dict |
31 | 32 | from pydantic import BaseModel, Field |
32 | 33 | from tqdm.auto import tqdm |
33 | 34 |
|
34 | 35 | from nemo_gym import PARENT_DIR |
35 | 36 | from nemo_gym.config_types import BaseNeMoGymCLIConfig |
36 | 37 | from nemo_gym.global_config import ( |
| 38 | + HEAD_SERVER_DEPS_KEY_NAME, |
37 | 39 | NEMO_GYM_CONFIG_DICT_ENV_VAR_NAME, |
38 | 40 | NEMO_GYM_CONFIG_PATH_ENV_VAR_NAME, |
39 | 41 | NEMO_GYM_RESERVED_TOP_LEVEL_KEYS, |
|
48 | 50 | ) |
49 | 51 |
|
50 | 52 |
|
51 | | -def _setup_env_command(dir_path: Path) -> str: # pragma: no cover |
| 53 | +def _capture_head_server_dependencies(global_config_dict: DictConfig) -> None: # pragma: no cover |
| 54 | + try: |
| 55 | + result = subprocess.run( |
| 56 | + ["uv", "pip", "freeze", "--exclude-editable"], |
| 57 | + capture_output=True, |
| 58 | + text=True, |
| 59 | + check=True, |
| 60 | + ) |
| 61 | + head_server_deps = result.stdout |
| 62 | + except Exception as e: |
| 63 | + print(f"Warning: Could not capture head server dependencies: {e}") |
| 64 | + head_server_deps = None |
| 65 | + |
| 66 | + with open_dict(global_config_dict): |
| 67 | + global_config_dict[HEAD_SERVER_DEPS_KEY_NAME] = head_server_deps |
| 68 | + |
| 69 | + |
| 70 | +def _setup_env_command(dir_path: Path, head_server_deps: Optional[str] = None) -> str: # pragma: no cover |
| 71 | + install_cmd = "uv pip install -r requirements.txt" |
| 72 | + if head_server_deps: |
| 73 | + install_cmd += f" --constraint <(cat << 'EOF'\n{head_server_deps}\nEOF\n)" |
| 74 | + |
52 | 75 | return f"""cd {dir_path} \\ |
53 | 76 | && uv venv --allow-existing \\ |
54 | 77 | && source .venv/bin/activate \\ |
55 | | - && uv pip install -r requirements.txt \\ |
| 78 | + && {install_cmd} \\ |
56 | 79 | """ |
57 | 80 |
|
58 | 81 |
|
59 | 82 | def _run_command(command: str, working_directory: Path) -> Popen: # pragma: no cover |
60 | 83 | custom_env = environ.copy() |
61 | 84 | custom_env["PYTHONPATH"] = f"{working_directory.absolute()}:{custom_env.get('PYTHONPATH', '')}" |
62 | | - print(f"Executing command:\n{command}\n") |
63 | 85 | return Popen(command, executable="/bin/bash", shell=True, env=custom_env) |
64 | 86 |
|
65 | 87 |
|
@@ -114,6 +136,9 @@ class RunHelper: # pragma: no cover |
114 | 136 | def start(self, global_config_dict_parser_config: GlobalConfigDictParserConfig) -> None: |
115 | 137 | global_config_dict = get_global_config_dict(global_config_dict_parser_config=global_config_dict_parser_config) |
116 | 138 |
|
| 139 | + # Capture head server dependencies and store in global config dict |
| 140 | + _capture_head_server_dependencies(global_config_dict) |
| 141 | + |
117 | 142 | # Assume Nemo Gym Run is for a single agent. |
118 | 143 | escaped_config_dict_yaml_str = shlex.quote(OmegaConf.to_yaml(global_config_dict)) |
119 | 144 |
|
@@ -149,7 +174,9 @@ def start(self, global_config_dict_parser_config: GlobalConfigDictParserConfig) |
149 | 174 |
|
150 | 175 | dir_path = PARENT_DIR / Path(first_key, second_key) |
151 | 176 |
|
152 | | - command = f"""{_setup_env_command(dir_path)} \\ |
| 177 | + head_server_deps = global_config_dict.get(HEAD_SERVER_DEPS_KEY_NAME) |
| 178 | + |
| 179 | + command = f"""{_setup_env_command(dir_path, head_server_deps)} \\ |
153 | 180 | && {NEMO_GYM_CONFIG_DICT_ENV_VAR_NAME}={escaped_config_dict_yaml_str} \\ |
154 | 181 | {NEMO_GYM_CONFIG_PATH_ENV_VAR_NAME}={shlex.quote(top_level_path)} \\ |
155 | 182 | python {str(entrypoint_fpath)}""" |
|
0 commit comments