File size: 8,015 Bytes
05c9ac2 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 |
import os
import shutil
import subprocess
import yaml
from sys import platform
from typing import List, Optional, Mapping
def get_unity_executable_path():
if platform == "darwin":
downloader_install_path = "./.Editor/Unity.app/Contents/MacOS/Unity"
else: # if platform == "linux":
downloader_install_path = "./.Editor/Unity"
if os.path.exists(downloader_install_path):
return downloader_install_path
raise FileNotFoundError("Can't find executable from unity-downloader-cli")
def get_base_path():
# We might need to do some more work here if the working directory ever changes
# E.g. take the full path and back out the main module main.
# But for now, this should work
return os.getcwd()
def get_base_output_path():
""" "
Returns the artifact folder to use for yamato jobs.
"""
return os.path.join(get_base_path(), "artifacts")
def run_standalone_build(
base_path: str,
verbose: bool = False,
output_path: str = None,
scene_path: str = None,
build_target: str = None,
log_output_path: Optional[str] = f"{get_base_output_path()}/standalone_build.txt",
) -> int:
"""
Run BuildStandalonePlayerOSX test to produce a player. The location defaults to
artifacts/standalonebuild/testPlayer.
"""
unity_exe = get_unity_executable_path()
print(f"Running BuildStandalonePlayer via {unity_exe}")
# enum values from https://docs.unity3d.com/2022.3/Documentation/ScriptReference/BuildTarget.html
build_target_to_enum: Mapping[Optional[str], str] = {
"mac": "StandaloneOSX",
"osx": "StandaloneOSX",
"linux": "StandaloneLinux64",
}
# Convert the short name to the official enum
# Just pass through if it's not on the list.
build_target_enum = build_target_to_enum.get(build_target, build_target)
test_args = [
unity_exe,
"-projectPath",
f"{base_path}/Project",
"-batchmode",
"-executeMethod",
"Unity.MLAgents.StandaloneBuildTest.BuildStandalonePlayerOSX",
]
if log_output_path:
os.makedirs(os.path.dirname(log_output_path), exist_ok=True)
subprocess.run(["touch", log_output_path])
test_args += ["-logfile", log_output_path]
else:
# Log to stdout
test_args += ["-logfile", "-"]
if output_path is not None:
output_path = os.path.join(get_base_output_path(), output_path)
test_args += ["--mlagents-build-output-path", output_path]
os.makedirs(os.path.dirname(output_path), exist_ok=True)
if scene_path is not None:
test_args += ["--mlagents-build-scene-path", scene_path]
if build_target_enum is not None:
test_args += ["--mlagents-build-target", build_target_enum]
print(f"{' '.join(test_args)} ...")
timeout = 30 * 60 # 30 minutes, just in case
res: subprocess.CompletedProcess = subprocess.run(test_args, timeout=timeout)
# Copy the default build name into the artifacts folder.
if output_path is None and res.returncode == 0:
exe_name = "testPlayer.app" if platform == "darwin" else "testPlayer"
shutil.move(
os.path.join(base_path, "Project", exe_name),
os.path.join(get_base_output_path(), exe_name),
)
# Print if we fail or want verbosity.
if verbose or res.returncode != 0:
if log_output_path:
subprocess.run(["cat", log_output_path])
return res.returncode
def find_executables(root_dir: str) -> List[str]:
"""
Try to find the player executable. This seems to vary between Unity versions.
"""
ignored_extension = frozenset([".dll", ".dylib", ".bundle"])
ignored_files = frozenset(["macblas"])
exes = []
for root, _, files in os.walk(root_dir):
for filename in files:
file_root, ext = os.path.splitext(filename)
if ext in ignored_extension or filename in ignored_files:
continue
file_path = os.path.join(root, filename)
if os.access(file_path, os.X_OK):
exes.append(file_path)
# Also check the input path
if os.access(root_dir, os.X_OK):
exes.append(root_dir)
return exes
def init_venv(
mlagents_python_version: str = None, extra_packages: Optional[List[str]] = None
) -> None:
"""
Install the necessary packages for the venv
:param mlagents_python_version: The version of mlagents python packcage to install.
If None, will do a local install, otherwise will install from pypi
:return:
"""
pip_commands = ["--upgrade pip", "--upgrade setuptools"]
if mlagents_python_version:
# install from pypi
pip_commands += [
f"mlagents=={mlagents_python_version}",
# TODO build these and publish to internal pypi
"tf2onnx==1.6.1",
]
else:
# Local install
pip_commands += ["-e ./ml-agents-envs", "-e ./ml-agents"]
if extra_packages:
pip_commands += extra_packages
for cmd in pip_commands:
pip_index_url = "--index-url https://artifactory.prd.it.unity3d.com/artifactory/api/pypi/pypi/simple"
print(f'Running "python3 -m pip install -q {cmd} {pip_index_url}"')
subprocess.check_call(
f"python3 -m pip install -q {cmd} {pip_index_url}", shell=True
)
def checkout_csharp_version(csharp_version):
"""
Checks out the specific git revision (usually a tag) for the C# package and Project.
If csharp_version is None, no changes are made.
:param csharp_version:
:return:
"""
if csharp_version is None:
return
csharp_tag = f"com.unity.ml-agents_{csharp_version}"
csharp_dirs = ["com.unity.ml-agents", "com.unity.ml-agents.extensions", "Project"]
for csharp_dir in csharp_dirs:
subprocess.check_call(f"rm -rf {csharp_dir}", shell=True)
# Allow the checkout to fail, since the extensions folder isn't availabe in 1.0.0
subprocess.call(f"git checkout {csharp_tag} -- {csharp_dir}", shell=True)
def undo_git_checkout():
"""
Clean up the git working directory.
"""
subprocess.check_call("git reset HEAD .", shell=True)
subprocess.check_call("git checkout -- .", shell=True)
# Ensure the cache isn't polluted with old compiled assemblies.
subprocess.check_call("rm -rf Project/Library", shell=True)
def override_config_file(src_path, dest_path, overrides):
"""
Override settings in a trainer config file. For example,
override_config_file(src_path, dest_path, max_steps=42)
will copy the config file at src_path to dest_path, but override the max_steps field to 42 for all brains.
"""
with open(src_path) as f:
configs = yaml.safe_load(f)
behavior_configs = configs["behaviors"]
for config in behavior_configs.values():
_override_config_dict(config, overrides)
with open(dest_path, "w") as f:
yaml.dump(configs, f)
def _override_config_dict(config, overrides):
for key, val in overrides.items():
if isinstance(val, dict):
_override_config_dict(config[key], val)
else:
config[key] = val
def override_legacy_config_file(python_version, src_path, dest_path, **kwargs):
"""
Override settings in a trainer config file, using an old version of the src_path. For example,
override_config_file("0.16.0", src_path, dest_path, max_steps=42)
will sync the file at src_path from version 0.16.0, copy it to dest_path, and override the
max_steps field to 42 for all brains.
"""
# Sync the old version of the file
python_tag = f"python-packages_{python_version}"
subprocess.check_call(f"git checkout {python_tag} -- {src_path}", shell=True)
with open(src_path) as f:
configs = yaml.safe_load(f)
for config in configs.values():
config.update(**kwargs)
with open(dest_path, "w") as f:
yaml.dump(configs, f)
|