forked from aws-deadline/deadline-cloud-for-3ds-max
-
Notifications
You must be signed in to change notification settings - Fork 0
/
depsBundle.py
154 lines (124 loc) · 5.58 KB
/
depsBundle.py
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
from __future__ import annotations
import re
import shutil
import subprocess
import sys
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import Any
SUPPORTED_PYTHON_VERSIONS = ["3.9", "3.10", "3.11"]
SUPPORTED_PLATFORMS = ["win_amd64"]
NATIVE_DEPENDENCIES = ["xxhash"]
def _get_project_dict() -> dict[str, Any]:
if sys.version_info < (3, 11):
with TemporaryDirectory() as toml_env:
toml_install_pip_args = ["pip", "install", "--target", toml_env, "toml"]
subprocess.run(toml_install_pip_args, check=True)
sys.path.insert(0, toml_env)
import toml
mode = "r"
else:
import tomllib as toml
mode = "rb"
with open("pyproject.toml", mode) as pyproject_toml:
return toml.load(pyproject_toml)
def _get_dependencies(pyproject_dict: dict[str, Any]) -> list[str]:
if "project" not in pyproject_dict:
raise Exception("pyproject.toml is missing project section")
if "dependencies" not in pyproject_dict["project"]:
raise Exception("pyproject.toml is missing dependencies section")
dependencies = pyproject_dict["project"]["dependencies"]
deps_noopenjd = filter(lambda dep: not dep.startswith("openjd"), dependencies)
return list(map(lambda dep: dep.replace(" ", ""), deps_noopenjd))
def _get_package_version_regex(package: str) -> re.Pattern:
return re.compile(rf"^{re.escape(package)} *(.*)$")
def _get_package_version(package: str, install_path: Path) -> str:
version_regex = _get_package_version_regex(package)
pip_args = ["pip", "list", "--path", str(install_path)]
output = subprocess.run(pip_args, check=True, capture_output=True).stdout.decode("utf-8")
for line in output.split("\n"):
match = version_regex.match(line)
if match:
return match.group(1)
raise Exception(f"Could not find version for package {package}")
def _build_base_environment(working_directory: Path, dependencies: list[str]) -> Path:
(working_directory / "base_env").mkdir()
base_env_path = working_directory / "base_env"
base_env_pip_args = [
"pip",
"install",
"--target",
str(base_env_path),
"--only-binary=:all:",
*dependencies,
]
subprocess.run(base_env_pip_args, check=True)
return base_env_path
def _download_native_dependencies(working_directory: Path, base_env: Path) -> list[Path]:
versioned_native_dependencies = [
f"{package_name}=={_get_package_version(package_name, base_env)}"
for package_name in NATIVE_DEPENDENCIES
]
native_dependency_paths = []
for version in SUPPORTED_PYTHON_VERSIONS:
for platform in SUPPORTED_PLATFORMS:
native_dependency_path = (
working_directory / "native" / f"{version.replace('.', '_')}_{platform}"
)
native_dependency_paths.append(native_dependency_path)
native_dependency_path.mkdir(parents=True)
native_dependency_pip_args = [
"pip",
"install",
"--target",
str(native_dependency_path),
"--platform",
platform,
"--python-version",
version,
"--only-binary=:all:",
*versioned_native_dependencies,
]
subprocess.run(native_dependency_pip_args, check=True)
return native_dependency_paths
def _copy_native_to_base_env(base_env: Path, native_dependency_paths: list[Path]) -> None:
for native_dependency_path in native_dependency_paths:
for file in native_dependency_path.rglob("*"):
if file.is_file():
relative = file.relative_to(native_dependency_path)
in_base_env = base_env / relative
if not in_base_env.exists():
in_base_env.parent.mkdir(parents=True, exist_ok=True)
shutil.copy(str(file), str(in_base_env))
def _get_zip_path(working_directory: Path, project_dict: dict[str, Any]) -> Path:
if "project" not in project_dict:
raise Exception("pyproject.toml is missing project section")
if "name" not in project_dict["project"]:
raise Exception("pyproject.toml is missing name section")
transformed_project_name = (
f"{project_dict['project']['name'].replace('-', '_')}_submitter-deps.zip"
)
return working_directory / transformed_project_name
def _zip_bundle(base_env: Path, zip_path: Path) -> None:
shutil.make_archive(str(zip_path.with_suffix("")), "zip", str(base_env))
def _copy_zip_to_destination(zip_path: Path) -> None:
dependency_bundle_dir = Path.cwd() / "dependency_bundle"
dependency_bundle_dir.mkdir(exist_ok=True)
zip_destination = dependency_bundle_dir / zip_path.name
if zip_destination.exists():
zip_destination.unlink()
shutil.copy(str(zip_path), str(zip_destination))
def build_deps_bundle() -> None:
with TemporaryDirectory() as working_directory:
working_directory = Path(working_directory)
project_dict = _get_project_dict()
dependencies = _get_dependencies(project_dict)
base_env = _build_base_environment(working_directory, dependencies)
native_dependency_paths = _download_native_dependencies(working_directory, base_env)
_copy_native_to_base_env(base_env, native_dependency_paths)
zip_path = _get_zip_path(working_directory, project_dict)
_zip_bundle(base_env, zip_path)
print(list(working_directory.glob("*")))
_copy_zip_to_destination(zip_path)
if __name__ == "__main__":
build_deps_bundle()