holoscan-cli 2.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- holoscan_cli/__init__.py +35 -0
- holoscan_cli/__main__.py +164 -0
- holoscan_cli/common/argparse_types.py +156 -0
- holoscan_cli/common/artifact_sources.py +160 -0
- holoscan_cli/common/constants.py +119 -0
- holoscan_cli/common/dockerutils.py +521 -0
- holoscan_cli/common/enum_types.py +49 -0
- holoscan_cli/common/exceptions.py +126 -0
- holoscan_cli/common/sdk_utils.py +195 -0
- holoscan_cli/common/utils.py +137 -0
- holoscan_cli/logging.json +37 -0
- holoscan_cli/nics/__init__.py +15 -0
- holoscan_cli/nics/nics.py +33 -0
- holoscan_cli/package-source.json +32 -0
- holoscan_cli/packager/__init__.py +15 -0
- holoscan_cli/packager/arguments.py +148 -0
- holoscan_cli/packager/config_reader.py +180 -0
- holoscan_cli/packager/container_builder.py +426 -0
- holoscan_cli/packager/manifest_files.py +217 -0
- holoscan_cli/packager/models.py +90 -0
- holoscan_cli/packager/package_command.py +197 -0
- holoscan_cli/packager/packager.py +124 -0
- holoscan_cli/packager/parameters.py +603 -0
- holoscan_cli/packager/platforms.py +426 -0
- holoscan_cli/packager/templates/Dockerfile.jinja2 +479 -0
- holoscan_cli/packager/templates/dockerignore +92 -0
- holoscan_cli/packager/templates/tools.sh +414 -0
- holoscan_cli/py.typed +14 -0
- holoscan_cli/runner/__init__.py +15 -0
- holoscan_cli/runner/resources.py +185 -0
- holoscan_cli/runner/run_command.py +207 -0
- holoscan_cli/runner/runner.py +340 -0
- holoscan_cli/version/__init__.py +15 -0
- holoscan_cli/version/version.py +53 -0
- holoscan_cli-2.9.0.dist-info/LICENSE +201 -0
- holoscan_cli-2.9.0.dist-info/METADATA +102 -0
- holoscan_cli-2.9.0.dist-info/RECORD +39 -0
- holoscan_cli-2.9.0.dist-info/WHEEL +4 -0
- holoscan_cli-2.9.0.dist-info/entry_points.txt +4 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2023-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ApplicationManifest:
|
|
20
|
+
def __init__(self):
|
|
21
|
+
self._data = {}
|
|
22
|
+
self._data["apiVersion"] = None
|
|
23
|
+
self._data["command"] = None
|
|
24
|
+
self._data["environment"] = None
|
|
25
|
+
self._data["input"] = None
|
|
26
|
+
self._data["liveness"] = None
|
|
27
|
+
self._data["output"] = None
|
|
28
|
+
self._data["readiness"] = None
|
|
29
|
+
self._data["sdk"] = None
|
|
30
|
+
self._data["sdkVersion"] = None
|
|
31
|
+
self._data["timeout"] = None
|
|
32
|
+
self._data["version"] = None
|
|
33
|
+
self._data["workingDirectory"] = None
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def api_version(self) -> str:
|
|
37
|
+
return self._data["apiVersion"]
|
|
38
|
+
|
|
39
|
+
@api_version.setter
|
|
40
|
+
def api_version(self, value: str):
|
|
41
|
+
self._data["apiVersion"] = value
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def command(self) -> str:
|
|
45
|
+
return self._data["command"]
|
|
46
|
+
|
|
47
|
+
@command.setter
|
|
48
|
+
def command(self, value: str):
|
|
49
|
+
self._data["command"] = value
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def environment(self) -> dict[str, str]:
|
|
53
|
+
return self._data["environment"]
|
|
54
|
+
|
|
55
|
+
@environment.setter
|
|
56
|
+
def environment(self, value: dict[str, str]):
|
|
57
|
+
self._data["environment"] = value
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def input(self) -> dict[str, str]: # noqa: A003
|
|
61
|
+
return self._data["input"]
|
|
62
|
+
|
|
63
|
+
@input.setter
|
|
64
|
+
def input(self, value: dict[str, str]): # noqa: A003
|
|
65
|
+
self._data["input"] = value
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def liveness(self) -> Any:
|
|
69
|
+
return self._data["liveness"]
|
|
70
|
+
|
|
71
|
+
@liveness.setter
|
|
72
|
+
def liveness(self, value: Any):
|
|
73
|
+
self._data["liveness"] = value
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def output(self) -> dict[str, str]:
|
|
77
|
+
return self._data["output"]
|
|
78
|
+
|
|
79
|
+
@output.setter
|
|
80
|
+
def output(self, value: dict[str, str]):
|
|
81
|
+
self._data["output"] = value
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def readiness(self) -> Any:
|
|
85
|
+
return self._data["readiness"]
|
|
86
|
+
|
|
87
|
+
@readiness.setter
|
|
88
|
+
def readiness(self, value: Any):
|
|
89
|
+
self._data["readiness"] = value
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def sdk(self) -> str:
|
|
93
|
+
return self._data["sdk"]
|
|
94
|
+
|
|
95
|
+
@sdk.setter
|
|
96
|
+
def sdk(self, value: str):
|
|
97
|
+
self._data["sdk"] = value
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def sdk_version(self) -> str:
|
|
101
|
+
return self._data["sdkVersion"]
|
|
102
|
+
|
|
103
|
+
@sdk_version.setter
|
|
104
|
+
def sdk_version(self, value: str):
|
|
105
|
+
self._data["sdkVersion"] = value
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def timeout(self) -> int:
|
|
109
|
+
return self._data["timeout"]
|
|
110
|
+
|
|
111
|
+
@timeout.setter
|
|
112
|
+
def timeout(self, value: int):
|
|
113
|
+
self._data["timeout"] = value
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def version(self) -> str:
|
|
117
|
+
return self._data["version"]
|
|
118
|
+
|
|
119
|
+
@version.setter
|
|
120
|
+
def version(self, value: str):
|
|
121
|
+
self._data["version"] = value
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def working_directory(self) -> str:
|
|
125
|
+
return self._data["workingDirectory"]
|
|
126
|
+
|
|
127
|
+
@working_directory.setter
|
|
128
|
+
def working_directory(self, value: str):
|
|
129
|
+
if isinstance(value, Path):
|
|
130
|
+
self._data["workingDirectory"] = str(value)
|
|
131
|
+
else:
|
|
132
|
+
self._data["workingDirectory"] = value
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def data(self) -> dict[str, Any]:
|
|
136
|
+
"""Returns all values for serializing to JSON"""
|
|
137
|
+
return self._data
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class PackageManifest:
|
|
141
|
+
def __init__(self):
|
|
142
|
+
self._data = {}
|
|
143
|
+
self._data["apiVersion"] = None
|
|
144
|
+
self._data["applicationRoot"] = None
|
|
145
|
+
self._data["modelRoot"] = None
|
|
146
|
+
self._data["models"] = None
|
|
147
|
+
self._data["resources"] = None
|
|
148
|
+
self._data["version"] = None
|
|
149
|
+
self._data["platformConfig"] = None
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def platform_config(self) -> str:
|
|
153
|
+
return self._data["platformConfig"]
|
|
154
|
+
|
|
155
|
+
@platform_config.setter
|
|
156
|
+
def platform_config(self, value: str):
|
|
157
|
+
self._data["platformConfig"] = value
|
|
158
|
+
|
|
159
|
+
@property
|
|
160
|
+
def api_version(self) -> str:
|
|
161
|
+
return self._data["apiVersion"]
|
|
162
|
+
|
|
163
|
+
@api_version.setter
|
|
164
|
+
def api_version(self, value: str):
|
|
165
|
+
self._data["apiVersion"] = value
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def application_root(self) -> str:
|
|
169
|
+
return self._data["applicationRoot"]
|
|
170
|
+
|
|
171
|
+
@application_root.setter
|
|
172
|
+
def application_root(self, value: str):
|
|
173
|
+
if isinstance(value, Path):
|
|
174
|
+
self._data["applicationRoot"] = str(value)
|
|
175
|
+
else:
|
|
176
|
+
self._data["applicationRoot"] = value
|
|
177
|
+
|
|
178
|
+
@property
|
|
179
|
+
def model_root(self) -> str:
|
|
180
|
+
return self._data["modelRoot"]
|
|
181
|
+
|
|
182
|
+
@model_root.setter
|
|
183
|
+
def model_root(self, value: str):
|
|
184
|
+
if isinstance(value, Path):
|
|
185
|
+
self._data["modelRoot"] = str(value)
|
|
186
|
+
else:
|
|
187
|
+
self._data["modelRoot"] = value
|
|
188
|
+
|
|
189
|
+
@property
|
|
190
|
+
def models(self) -> dict[str, str]:
|
|
191
|
+
return self._data["models"]
|
|
192
|
+
|
|
193
|
+
@models.setter
|
|
194
|
+
def models(self, value: dict[str, str]):
|
|
195
|
+
self._data["models"] = value
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
def resources(self) -> Any:
|
|
199
|
+
return self._data["resources"]
|
|
200
|
+
|
|
201
|
+
@resources.setter
|
|
202
|
+
def resources(self, value: Any):
|
|
203
|
+
"""Resources are copied from application configuration file directly."""
|
|
204
|
+
self._data["resources"] = value
|
|
205
|
+
|
|
206
|
+
@property
|
|
207
|
+
def version(self) -> str:
|
|
208
|
+
return self._data["version"]
|
|
209
|
+
|
|
210
|
+
@version.setter
|
|
211
|
+
def version(self, value: str):
|
|
212
|
+
self._data["version"] = value
|
|
213
|
+
|
|
214
|
+
@property
|
|
215
|
+
def data(self) -> dict[str, Any]:
|
|
216
|
+
"""Returns all values for serializing to JSON"""
|
|
217
|
+
return self._data
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2023-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
import logging
|
|
16
|
+
import os
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Optional
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger("packager")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Models:
|
|
24
|
+
"""
|
|
25
|
+
Builds model list from a given path where the path could be a model file, a directory that
|
|
26
|
+
contains a model or a directory with multiple models where each model contains within its own
|
|
27
|
+
subdirectory.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def build(self, models_path: Path) -> Optional[dict[str, Path]]:
|
|
31
|
+
"""Checks if the given path is a file or a directory.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
models_path (Path): A user given path that contains one or more models.
|
|
35
|
+
Returns:
|
|
36
|
+
Optional[Dict[str, Path]]: Returns None if no path is given. Otherwise, returns a
|
|
37
|
+
dictionary where the key is the name of the model and the value contains the path
|
|
38
|
+
to the model.
|
|
39
|
+
"""
|
|
40
|
+
if models_path is not None:
|
|
41
|
+
logger.info(f"Scanning for models in {models_path}...")
|
|
42
|
+
models: dict[str, Path] = {}
|
|
43
|
+
if models_path.is_file():
|
|
44
|
+
self._configure_model_file(models_path, models)
|
|
45
|
+
elif models_path.is_dir():
|
|
46
|
+
self._configure_model_dir(models_path, models)
|
|
47
|
+
|
|
48
|
+
return models
|
|
49
|
+
else:
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
def _configure_model_dir(self, models_path: Path, models: dict[str, Path]):
|
|
53
|
+
"""
|
|
54
|
+
Iterate through the given directory to scan for models.
|
|
55
|
+
If files are found within the directory, we simply assume that all files within the given
|
|
56
|
+
directory contains a model and sets the name of the model to the name of the given
|
|
57
|
+
directory.
|
|
58
|
+
|
|
59
|
+
Any subdirectories found within the given directory are treated as a separate model and the
|
|
60
|
+
name of the subdirectory is set to be the name of the model.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
models_path (Path): Path to the model file.
|
|
64
|
+
models (Dict[str, Path]): Where models are added to.
|
|
65
|
+
"""
|
|
66
|
+
model_dirs = os.listdir(models_path)
|
|
67
|
+
|
|
68
|
+
for model_dir in model_dirs:
|
|
69
|
+
if os.path.isfile(models_path / model_dir):
|
|
70
|
+
model_name = models_path.resolve().stem
|
|
71
|
+
models[model_name] = models_path
|
|
72
|
+
logger.debug(f"Model {model_name}={models_path} added.")
|
|
73
|
+
elif os.path.isdir(models_path / model_dir):
|
|
74
|
+
model_path = models_path / model_dir
|
|
75
|
+
model_name = model_dir
|
|
76
|
+
models[model_name] = model_path
|
|
77
|
+
logger.debug(f"Model {model_name}={model_path} added.")
|
|
78
|
+
|
|
79
|
+
def _configure_model_file(self, models_path: Path, models: dict[str, Path]):
|
|
80
|
+
"""
|
|
81
|
+
Adds a new model to 'models' object where the model name is the name of the given file,
|
|
82
|
+
and the value is the path to the given model file.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
models_path (Path): Path to the model file.
|
|
86
|
+
models (Dict[str, Path]): Where the model is added to.
|
|
87
|
+
"""
|
|
88
|
+
model_name = models_path.stem
|
|
89
|
+
models[model_name] = models_path
|
|
90
|
+
logger.debug(f"Model file {model_name}={models[model_name]} added.")
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2023-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
import argparse
|
|
16
|
+
import logging
|
|
17
|
+
from argparse import ArgumentParser, _SubParsersAction
|
|
18
|
+
|
|
19
|
+
from packaging.version import Version
|
|
20
|
+
|
|
21
|
+
from ..common.argparse_types import (
|
|
22
|
+
valid_dir_path,
|
|
23
|
+
valid_existing_dir_path,
|
|
24
|
+
valid_existing_path,
|
|
25
|
+
valid_platform_config,
|
|
26
|
+
valid_platforms,
|
|
27
|
+
valid_sdk_type,
|
|
28
|
+
)
|
|
29
|
+
from ..common.constants import SDK
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger("packager")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def create_package_parser(
|
|
35
|
+
subparser: _SubParsersAction, command: str, parents: list[ArgumentParser]
|
|
36
|
+
) -> ArgumentParser:
|
|
37
|
+
parser: ArgumentParser = subparser.add_parser(
|
|
38
|
+
command, formatter_class=argparse.HelpFormatter, parents=parents, add_help=False
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
parser.add_argument(
|
|
42
|
+
"application",
|
|
43
|
+
type=valid_existing_path,
|
|
44
|
+
help="Holoscan application path: Python application directory with __main__.py, "
|
|
45
|
+
"Python file, C++ source directory with CMakeLists.txt, or path to an executable.",
|
|
46
|
+
)
|
|
47
|
+
parser.add_argument(
|
|
48
|
+
"--config",
|
|
49
|
+
"-c",
|
|
50
|
+
required=True,
|
|
51
|
+
type=valid_existing_path,
|
|
52
|
+
help="Holoscan application configuration file (.yaml)",
|
|
53
|
+
)
|
|
54
|
+
parser.add_argument(
|
|
55
|
+
"--docs",
|
|
56
|
+
"-d",
|
|
57
|
+
type=valid_existing_dir_path,
|
|
58
|
+
help="path to a directory containing user documentation and/or licenses.",
|
|
59
|
+
)
|
|
60
|
+
parser.add_argument(
|
|
61
|
+
"--models",
|
|
62
|
+
"-m",
|
|
63
|
+
type=valid_existing_path,
|
|
64
|
+
help="path to a model file or a directory containing all models as subdirectories",
|
|
65
|
+
)
|
|
66
|
+
parser.add_argument(
|
|
67
|
+
"--platform",
|
|
68
|
+
type=valid_platforms,
|
|
69
|
+
required=True,
|
|
70
|
+
help="target platform(s) for the build output separated by comma. "
|
|
71
|
+
f"Valid values: {str.join(', ', SDK.PLATFORMS)}.",
|
|
72
|
+
)
|
|
73
|
+
parser.add_argument(
|
|
74
|
+
"--platform-config",
|
|
75
|
+
type=valid_platform_config,
|
|
76
|
+
help="target platform configuration for the build output. "
|
|
77
|
+
f"Valid values: {str.join(', ', SDK.PLATFORM_CONFIGS)}.",
|
|
78
|
+
)
|
|
79
|
+
parser.add_argument(
|
|
80
|
+
"--add",
|
|
81
|
+
action="append",
|
|
82
|
+
dest="additional_libs",
|
|
83
|
+
type=valid_existing_dir_path,
|
|
84
|
+
help="include additional library files, python files into the application directory.",
|
|
85
|
+
)
|
|
86
|
+
parser.add_argument(
|
|
87
|
+
"--timeout", type=int, help="override default application timeout"
|
|
88
|
+
)
|
|
89
|
+
parser.add_argument(
|
|
90
|
+
"--version", type=Version, help="set the version of the application"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
advanced_group = parser.add_argument_group(title="advanced build options")
|
|
94
|
+
advanced_group.add_argument(
|
|
95
|
+
"--base-image",
|
|
96
|
+
type=str,
|
|
97
|
+
help="base image name for the packaged application.",
|
|
98
|
+
)
|
|
99
|
+
advanced_group.add_argument(
|
|
100
|
+
"--build-image",
|
|
101
|
+
type=str,
|
|
102
|
+
help="container image name for building the C++ application.",
|
|
103
|
+
)
|
|
104
|
+
advanced_group.add_argument(
|
|
105
|
+
"--includes",
|
|
106
|
+
nargs="*",
|
|
107
|
+
default=[],
|
|
108
|
+
choices=["debug", "holoviz", "torch", "onnx"],
|
|
109
|
+
help="additional packages to include in the container.",
|
|
110
|
+
)
|
|
111
|
+
advanced_group.add_argument(
|
|
112
|
+
"--build-cache",
|
|
113
|
+
type=valid_dir_path,
|
|
114
|
+
default="~/.holoscan_build_cache",
|
|
115
|
+
help="path of the local directory where build cache gets stored.",
|
|
116
|
+
)
|
|
117
|
+
advanced_group.add_argument(
|
|
118
|
+
"--cmake-args",
|
|
119
|
+
type=str,
|
|
120
|
+
help='additional CMAKE build arguments. E.g. "-DCMAKE_BUILD_TYPE=DEBUG -DCMAKE_ARG=VALUE"',
|
|
121
|
+
)
|
|
122
|
+
advanced_group.add_argument(
|
|
123
|
+
"--holoscan-sdk-file",
|
|
124
|
+
type=valid_existing_path,
|
|
125
|
+
help="path to the Holoscan SDK Debian or PyPI package. "
|
|
126
|
+
"If not specified, the packager downloads "
|
|
127
|
+
"the SDK file from the internet based on the SDK version.",
|
|
128
|
+
)
|
|
129
|
+
advanced_group.add_argument(
|
|
130
|
+
"--monai-deploy-sdk-file",
|
|
131
|
+
type=valid_existing_path,
|
|
132
|
+
help="path to the MONAI Deploy App SDK PyPI package. "
|
|
133
|
+
"If not specified, the packager downloads "
|
|
134
|
+
"the SDK file from the internet based on the SDK version.",
|
|
135
|
+
)
|
|
136
|
+
advanced_group.add_argument(
|
|
137
|
+
"--no-cache",
|
|
138
|
+
"-n",
|
|
139
|
+
dest="no_cache",
|
|
140
|
+
action="store_true",
|
|
141
|
+
help="do not use cache when building image",
|
|
142
|
+
)
|
|
143
|
+
advanced_group.add_argument(
|
|
144
|
+
"--sdk",
|
|
145
|
+
type=valid_sdk_type,
|
|
146
|
+
help="SDK for building the application: Holoscan or MONAI-Deploy. "
|
|
147
|
+
f"Valid values: {str.join(', ', SDK.SDKS)}.",
|
|
148
|
+
)
|
|
149
|
+
advanced_group.add_argument(
|
|
150
|
+
"--source",
|
|
151
|
+
type=str,
|
|
152
|
+
help="override Debian package, build container image and run container image from a "
|
|
153
|
+
"JSON formatted file or a secured web server (HTTPS).",
|
|
154
|
+
)
|
|
155
|
+
advanced_group.add_argument(
|
|
156
|
+
"--sdk-version",
|
|
157
|
+
type=Version,
|
|
158
|
+
help="set the version of the SDK that is used to build and package the application. "
|
|
159
|
+
"If not specified, the packager attempts to detect the installed version.",
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
output_group = parser.add_argument_group(title="output options")
|
|
163
|
+
output_group.add_argument(
|
|
164
|
+
"--output",
|
|
165
|
+
"-o",
|
|
166
|
+
type=valid_existing_dir_path,
|
|
167
|
+
help="output directory where result images will be written.",
|
|
168
|
+
)
|
|
169
|
+
output_group.add_argument(
|
|
170
|
+
"--tag",
|
|
171
|
+
"-t",
|
|
172
|
+
required=True,
|
|
173
|
+
type=str,
|
|
174
|
+
help="name of the image and a optional tag (format: name[:tag]).",
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
user_group = parser.add_argument_group(title="security options")
|
|
178
|
+
user_group.add_argument(
|
|
179
|
+
"--username",
|
|
180
|
+
type=str,
|
|
181
|
+
default="holoscan",
|
|
182
|
+
help="username to be created in the container execution context.",
|
|
183
|
+
)
|
|
184
|
+
user_group.add_argument(
|
|
185
|
+
"--uid",
|
|
186
|
+
type=str,
|
|
187
|
+
default=1000,
|
|
188
|
+
help="UID associated with the username. (default:1000)",
|
|
189
|
+
)
|
|
190
|
+
user_group.add_argument(
|
|
191
|
+
"--gid",
|
|
192
|
+
type=str,
|
|
193
|
+
default=1000,
|
|
194
|
+
help="GID associated with the username. (default:1000)",
|
|
195
|
+
)
|
|
196
|
+
parser.set_defaults(no_cache=False)
|
|
197
|
+
return parser
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2023-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
import json
|
|
16
|
+
import logging
|
|
17
|
+
import os
|
|
18
|
+
import sys
|
|
19
|
+
import tempfile
|
|
20
|
+
from argparse import Namespace
|
|
21
|
+
|
|
22
|
+
from ..common.enum_types import ApplicationType
|
|
23
|
+
from ..common.utils import print_manifest_json
|
|
24
|
+
from .arguments import PackagingArguments
|
|
25
|
+
from .container_builder import CppAppBuilder, PythonAppBuilder
|
|
26
|
+
from .manifest_files import ApplicationManifest, PackageManifest
|
|
27
|
+
from .parameters import PlatformBuildResults
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger("packager")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _build_image(args: PackagingArguments, temp_dir: str) -> list[PlatformBuildResults]:
|
|
33
|
+
"""Creates dockerfile and builds HAP/MONAI Application Package (HAP/MAP) image
|
|
34
|
+
Args:
|
|
35
|
+
args (dict): Input arguments for Packager
|
|
36
|
+
temp_dir (str): Temporary directory to build MAP
|
|
37
|
+
"""
|
|
38
|
+
if (
|
|
39
|
+
args.build_parameters.application_type == ApplicationType.PythonFile
|
|
40
|
+
or args.build_parameters.application_type == ApplicationType.PythonModule
|
|
41
|
+
):
|
|
42
|
+
builder = PythonAppBuilder(args.build_parameters, temp_dir)
|
|
43
|
+
elif (
|
|
44
|
+
args.build_parameters.application_type == ApplicationType.Binary
|
|
45
|
+
or args.build_parameters.application_type == ApplicationType.CppCMake
|
|
46
|
+
):
|
|
47
|
+
builder = CppAppBuilder(args.build_parameters, temp_dir)
|
|
48
|
+
|
|
49
|
+
results = [builder.build(platform) for platform in args.platforms]
|
|
50
|
+
return results
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _create_app_manifest(manifest: ApplicationManifest, temp_dir: str):
|
|
54
|
+
"""Creates Application manifest .json file
|
|
55
|
+
Args:
|
|
56
|
+
manifest (Dict): Input arguments for Packager
|
|
57
|
+
temp_dir (str): Temporary directory to build MAP
|
|
58
|
+
"""
|
|
59
|
+
map_folder_path = os.path.join(temp_dir, "map")
|
|
60
|
+
os.makedirs(map_folder_path, exist_ok=True)
|
|
61
|
+
with open(os.path.join(map_folder_path, "app.json"), "w") as app_manifest_file:
|
|
62
|
+
app_manifest_file.write(json.dumps(manifest.data))
|
|
63
|
+
|
|
64
|
+
print_manifest_json(manifest.data, "app.json")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _create_package_manifest(manifest: PackageManifest, temp_dir: str):
|
|
68
|
+
"""Creates package manifest .json file
|
|
69
|
+
Args:
|
|
70
|
+
manifest (Dict): Input arguments for Packager
|
|
71
|
+
temp_dir (str): Temporary directory to build MAP
|
|
72
|
+
"""
|
|
73
|
+
map_folder_path = os.path.join(temp_dir, "map")
|
|
74
|
+
os.makedirs(map_folder_path, exist_ok=True)
|
|
75
|
+
with open(os.path.join(temp_dir, "map", "pkg.json"), "w") as package_manifest_file:
|
|
76
|
+
package_manifest_file.write(json.dumps(manifest.data))
|
|
77
|
+
|
|
78
|
+
print_manifest_json(manifest.data, "pkg.json")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _package_application(args: Namespace):
|
|
82
|
+
"""Driver function for invoking all functions for creating and
|
|
83
|
+
building the Holoscan Application package image
|
|
84
|
+
Args:
|
|
85
|
+
args (Namespace): Input arguments for Packager from CLI
|
|
86
|
+
"""
|
|
87
|
+
# Initialize arguments for package
|
|
88
|
+
with tempfile.TemporaryDirectory(
|
|
89
|
+
prefix="holoscan_tmp", dir=tempfile.gettempdir()
|
|
90
|
+
) as temp_dir:
|
|
91
|
+
packaging_args = PackagingArguments(args, temp_dir)
|
|
92
|
+
|
|
93
|
+
# Create Manifest Files
|
|
94
|
+
_create_app_manifest(packaging_args.application_manifest, temp_dir)
|
|
95
|
+
_create_package_manifest(packaging_args.package_manifest, temp_dir)
|
|
96
|
+
|
|
97
|
+
results = _build_image(packaging_args, temp_dir)
|
|
98
|
+
|
|
99
|
+
logger.info("Build Summary:")
|
|
100
|
+
for result in results:
|
|
101
|
+
if result.succeeded:
|
|
102
|
+
print(
|
|
103
|
+
f"""\nPlatform: {result.parameters.platform.value}/{result.parameters.platform_config.value}
|
|
104
|
+
Status: Succeeded
|
|
105
|
+
Docker Tag: {result.docker_tag if result.docker_tag is not None else "N/A"}
|
|
106
|
+
Tarball: {result.tarball_filenaem}""" # noqa: E501
|
|
107
|
+
)
|
|
108
|
+
else:
|
|
109
|
+
print(
|
|
110
|
+
f"""\nPlatform: {result.parameters.platform.value}/{result.parameters.platform_config.value}
|
|
111
|
+
Status: Failure
|
|
112
|
+
Error: {result.error}
|
|
113
|
+
""" # noqa: E501
|
|
114
|
+
)
|
|
115
|
+
sys.exit(1)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def execute_package_command(args: Namespace):
|
|
119
|
+
try:
|
|
120
|
+
_package_application(args)
|
|
121
|
+
except Exception as e:
|
|
122
|
+
logger.debug(e, exc_info=True)
|
|
123
|
+
logger.error(f"Error packaging application:\n\n{str(e)}")
|
|
124
|
+
sys.exit(1)
|