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.
Files changed (39) hide show
  1. holoscan_cli/__init__.py +35 -0
  2. holoscan_cli/__main__.py +164 -0
  3. holoscan_cli/common/argparse_types.py +156 -0
  4. holoscan_cli/common/artifact_sources.py +160 -0
  5. holoscan_cli/common/constants.py +119 -0
  6. holoscan_cli/common/dockerutils.py +521 -0
  7. holoscan_cli/common/enum_types.py +49 -0
  8. holoscan_cli/common/exceptions.py +126 -0
  9. holoscan_cli/common/sdk_utils.py +195 -0
  10. holoscan_cli/common/utils.py +137 -0
  11. holoscan_cli/logging.json +37 -0
  12. holoscan_cli/nics/__init__.py +15 -0
  13. holoscan_cli/nics/nics.py +33 -0
  14. holoscan_cli/package-source.json +32 -0
  15. holoscan_cli/packager/__init__.py +15 -0
  16. holoscan_cli/packager/arguments.py +148 -0
  17. holoscan_cli/packager/config_reader.py +180 -0
  18. holoscan_cli/packager/container_builder.py +426 -0
  19. holoscan_cli/packager/manifest_files.py +217 -0
  20. holoscan_cli/packager/models.py +90 -0
  21. holoscan_cli/packager/package_command.py +197 -0
  22. holoscan_cli/packager/packager.py +124 -0
  23. holoscan_cli/packager/parameters.py +603 -0
  24. holoscan_cli/packager/platforms.py +426 -0
  25. holoscan_cli/packager/templates/Dockerfile.jinja2 +479 -0
  26. holoscan_cli/packager/templates/dockerignore +92 -0
  27. holoscan_cli/packager/templates/tools.sh +414 -0
  28. holoscan_cli/py.typed +14 -0
  29. holoscan_cli/runner/__init__.py +15 -0
  30. holoscan_cli/runner/resources.py +185 -0
  31. holoscan_cli/runner/run_command.py +207 -0
  32. holoscan_cli/runner/runner.py +340 -0
  33. holoscan_cli/version/__init__.py +15 -0
  34. holoscan_cli/version/version.py +53 -0
  35. holoscan_cli-2.9.0.dist-info/LICENSE +201 -0
  36. holoscan_cli-2.9.0.dist-info/METADATA +102 -0
  37. holoscan_cli-2.9.0.dist-info/RECORD +39 -0
  38. holoscan_cli-2.9.0.dist-info/WHEEL +4 -0
  39. holoscan_cli-2.9.0.dist-info/entry_points.txt +4 -0
@@ -0,0 +1,207 @@
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 argparse import ArgumentParser, HelpFormatter, _SubParsersAction
18
+
19
+ from ..common import argparse_types
20
+ from ..common.argparse_types import valid_existing_path
21
+
22
+ logger = logging.getLogger("runner")
23
+
24
+
25
+ def create_run_parser(
26
+ subparser: _SubParsersAction, command: str, parents: list[ArgumentParser]
27
+ ) -> ArgumentParser:
28
+ parser: ArgumentParser = subparser.add_parser(
29
+ command, formatter_class=HelpFormatter, parents=parents, add_help=False
30
+ )
31
+
32
+ parser.add_argument("map", metavar="<image[:tag]>", help="HAP/MAP image name.")
33
+
34
+ parser.add_argument(
35
+ "--address",
36
+ dest="address",
37
+ help="address ('[<IP or hostname>][:<port>]') of the App Driver. If not specified, "
38
+ "the App Driver uses the default host address ('0.0.0.0') with the default port "
39
+ "number ('8765').",
40
+ )
41
+
42
+ parser.add_argument(
43
+ "--driver",
44
+ dest="driver",
45
+ action="store_true",
46
+ default=False,
47
+ help="run the App Driver on the current machine. Can be used together with the "
48
+ "'--worker' option "
49
+ "to run both the App Driver and the App Worker on the same machine.",
50
+ )
51
+
52
+ parser.add_argument(
53
+ "-i",
54
+ "--input",
55
+ metavar="<input>",
56
+ type=argparse_types.valid_dir_path,
57
+ help="input data directory path.",
58
+ required=False,
59
+ )
60
+
61
+ parser.add_argument(
62
+ "-o",
63
+ "--output",
64
+ metavar="<output>",
65
+ type=argparse_types.valid_dir_path,
66
+ help="output data directory path.",
67
+ required=False,
68
+ )
69
+
70
+ parser.add_argument(
71
+ "-f",
72
+ "--fragments",
73
+ dest="fragments",
74
+ help="comma-separated names of the fragments to be executed by the App Worker. "
75
+ "If not specified, only one fragment (selected by the App Driver) will be executed. "
76
+ "'all' can be used to run all the fragments.",
77
+ )
78
+
79
+ parser.add_argument(
80
+ "--worker",
81
+ dest="worker",
82
+ action="store_true",
83
+ default=False,
84
+ help="run the App Worker.",
85
+ )
86
+
87
+ parser.add_argument(
88
+ "--worker-address",
89
+ dest="worker_address",
90
+ help="address (`[<IP or hostname>][:<port>]`) of the App Worker. If not specified, the App "
91
+ "Worker uses the default host address ('0.0.0.0') with a randomly chosen port number "
92
+ "between 10000 and 32767 that is not currently in use.",
93
+ )
94
+
95
+ advanced_group = parser.add_argument_group(title="advanced run options")
96
+
97
+ advanced_group.add_argument(
98
+ "--config",
99
+ dest="config",
100
+ type=valid_existing_path,
101
+ help="path to the configuration file. This will override the configuration file embedded "
102
+ "in the application.",
103
+ )
104
+
105
+ advanced_group.add_argument(
106
+ "--name",
107
+ dest="name",
108
+ help="name and hostname of the container to create.",
109
+ )
110
+ advanced_group.add_argument(
111
+ "--health-check",
112
+ dest="health_check",
113
+ action="store_true",
114
+ default="False",
115
+ help="enable the health check service for a distributed application. (default: False)",
116
+ )
117
+ advanced_group.add_argument(
118
+ "-n",
119
+ "--network",
120
+ dest="network",
121
+ default="host",
122
+ help="name of the Docker network this application will be connected to. (default: host)",
123
+ )
124
+ advanced_group.add_argument(
125
+ "--nic",
126
+ dest="nic",
127
+ help="name of the network interface to use with a distributed multi-fragment application. "
128
+ "This option sets UCX_NET_DEVICES environment variable with the value specified.",
129
+ )
130
+ advanced_group.add_argument(
131
+ "--use-all-nics",
132
+ dest="use_all_nics",
133
+ action="store_true",
134
+ default=False,
135
+ help="allow UCX to control the selection of network interface cards for data "
136
+ "transfer. Otherwise, the network interface card specified with '--nic' is used. "
137
+ "This option sets UCX_CM_USE_ALL_DEVICES to 'y'. (default: False)",
138
+ )
139
+ advanced_group.add_argument(
140
+ "-r",
141
+ "--render",
142
+ dest="render",
143
+ action="store_true",
144
+ default=False,
145
+ help="enable rendering (default: False); runs the container with required flags to enable "
146
+ "rendering of graphics.",
147
+ )
148
+ advanced_group.add_argument(
149
+ "-q",
150
+ "--quiet",
151
+ dest="quiet",
152
+ action="store_true",
153
+ default=False,
154
+ help="suppress the STDOUT and print only STDERR from the application. (default: False)",
155
+ )
156
+ advanced_group.add_argument(
157
+ "--shm-size",
158
+ dest="shm_size",
159
+ help="set the size of /dev/shm. The format is "
160
+ "<number(int,float)>[MB|m|GB|g|Mi|MiB|Gi|GiB]. "
161
+ "Use 'config' to read the shared memory value defined in the app.json manifest. "
162
+ "If not specified, the container is launched using '--ipc=host' with host system's "
163
+ "/dev/shm mounted.",
164
+ )
165
+ advanced_group.add_argument(
166
+ "--terminal",
167
+ dest="terminal",
168
+ action="store_true",
169
+ default=False,
170
+ help="enter terminal with all configured volume mappings and environment variables. "
171
+ "(default: False)",
172
+ )
173
+ advanced_group.add_argument(
174
+ "--device",
175
+ dest="device",
176
+ nargs="+",
177
+ action="extend",
178
+ help="""Mount additional devices.
179
+ For example:
180
+ --device ajantv* to mount all AJA capture cards.
181
+ --device ajantv0 ajantv1 to mount AJA capture card 0 and 1.
182
+ --device video1 to mount V4L2 video device 1. """,
183
+ )
184
+ advanced_group.add_argument(
185
+ "--gpus",
186
+ dest="gpus",
187
+ help="""Override the value of NVIDIA_VISIBLE_DEVICES environment variable.
188
+ default: the value specified in the package manifest file or 'all' if not specified.
189
+ Refer to https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/docker-specialized.html#gpu-enumeration
190
+ for all available options.""",
191
+ )
192
+
193
+ user_group = parser.add_argument_group(title="security options")
194
+ user_group.add_argument(
195
+ "--uid",
196
+ type=str,
197
+ default=os.getuid(),
198
+ help=f"run the container with the UID. (default:{os.getuid()})",
199
+ )
200
+ user_group.add_argument(
201
+ "--gid",
202
+ type=str,
203
+ default=os.getgid(),
204
+ help=f"run the container with the GID. (default:{os.getgid()})",
205
+ )
206
+
207
+ return parser
@@ -0,0 +1,340 @@
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 re
19
+ import shutil
20
+ import sys
21
+ import tempfile
22
+ from argparse import Namespace
23
+ from glob import glob
24
+ from pathlib import Path
25
+ from typing import Optional
26
+
27
+ from ..common.dockerutils import create_or_use_network, docker_run, image_exists
28
+ from ..common.exceptions import ManifestReadError, UnmatchedDeviceError
29
+ from ..common.utils import (
30
+ compare_versions,
31
+ get_requested_gpus,
32
+ print_manifest_json,
33
+ run_cmd,
34
+ run_cmd_output,
35
+ )
36
+ from .resources import get_shared_memory_size
37
+
38
+ logger = logging.getLogger("runner")
39
+
40
+
41
+ def _fetch_map_manifest(map_name: str) -> tuple[dict, dict]:
42
+ """
43
+ Execute HAP/MAP and fetch the manifest files.
44
+
45
+ Args:
46
+ map_name: HAP/MAP image name.
47
+
48
+ Returns:
49
+ app_info: application manifest as a python dict
50
+ pkg_info: package manifest as a python dict
51
+ """
52
+
53
+ def _ensure_valid_exit_code(returncode: int) -> None:
54
+ if returncode != 0:
55
+ raise ManifestReadError("Error reading manifest file from the package.")
56
+
57
+ logger.info("Reading HAP/MAP manifest...")
58
+
59
+ with tempfile.TemporaryDirectory() as info_dir:
60
+ docker_id = run_cmd_output(["docker", "create", map_name]).strip()
61
+ returncode = run_cmd(
62
+ [
63
+ "docker",
64
+ "cp",
65
+ f"{docker_id}:/etc/holoscan/app.json",
66
+ f"{info_dir}/app.json",
67
+ ]
68
+ )
69
+ _ensure_valid_exit_code(returncode)
70
+ returncode = run_cmd(
71
+ [
72
+ "docker",
73
+ "cp",
74
+ f"{docker_id}:/etc/holoscan/pkg.json",
75
+ f"{info_dir}/pkg.json",
76
+ ]
77
+ )
78
+ _ensure_valid_exit_code(returncode)
79
+ returncode = run_cmd(["docker", "rm", "-v", docker_id])
80
+ _ensure_valid_exit_code(returncode)
81
+
82
+ app_json = Path(f"{info_dir}/app.json")
83
+ pkg_json = Path(f"{info_dir}/pkg.json")
84
+
85
+ app_info = json.loads(app_json.read_text())
86
+ pkg_info = json.loads(pkg_json.read_text())
87
+
88
+ print_manifest_json(app_info, "app.json")
89
+ print_manifest_json(pkg_info, "pkg.json")
90
+
91
+ return app_info, pkg_info
92
+
93
+
94
+ def _run_app(args: Namespace, app_info: dict, pkg_info: dict):
95
+ """
96
+ Executes the Holoscan Application Package.
97
+
98
+ Args:
99
+ args: user arguments
100
+ app_info: application manifest dictionary
101
+ pkg_info: package manifest dictionary
102
+
103
+ Returns:
104
+ returncode: command returncode
105
+ """
106
+
107
+ map_name: str = args.map
108
+ input_path: Path = args.input
109
+ output_path: Path = args.output
110
+ quiet: bool = args.quiet
111
+ driver: bool = args.driver
112
+ worker: bool = args.worker
113
+ health_check: bool = args.health_check
114
+ fragments: Optional[str] = args.fragments
115
+ network: str = create_or_use_network(args.network, map_name)
116
+ nic: Optional[str] = args.nic if args.nic else None
117
+ use_all_nics: bool = args.use_all_nics
118
+ gpus: Optional[str] = args.gpus if args.gpus else None
119
+ config: Optional[Path] = args.config if args.config else None
120
+ address: Optional[str] = args.address if args.address else None
121
+ worker_address: Optional[str] = args.worker_address if args.worker_address else None
122
+ render: bool = args.render
123
+ user: str = f"{args.uid}:{args.gid}"
124
+ hostname: Optional[str] = "driver" if driver else None
125
+ terminal: bool = args.terminal
126
+ platform_config: str = pkg_info.get("platformConfig")
127
+ shared_memory_size: Optional[str] = (
128
+ get_shared_memory_size(pkg_info, worker, driver, fragments, args.shm_size)
129
+ if args.shm_size
130
+ else None
131
+ )
132
+
133
+ commands = []
134
+ devices = _lookup_devices(args.device) if args.device is not None else []
135
+
136
+ if driver:
137
+ commands.append("--driver")
138
+ logger.info("Application running in Driver mode")
139
+ if worker:
140
+ commands.append("--worker")
141
+ logger.info("Application running in Worker mode")
142
+ if fragments:
143
+ commands.append("--fragments")
144
+ commands.append(fragments)
145
+ logger.info(f"Configured fragments: {fragments}")
146
+ if address:
147
+ commands.append("--address")
148
+ commands.append(address)
149
+ logger.info(f"App Driver address and port: {address}")
150
+ if worker_address:
151
+ commands.append("--worker_address")
152
+ commands.append(worker_address)
153
+ logger.info(f"App Worker address and port: {worker_address}")
154
+
155
+ docker_run(
156
+ hostname,
157
+ map_name,
158
+ input_path,
159
+ output_path,
160
+ app_info,
161
+ pkg_info,
162
+ quiet,
163
+ commands,
164
+ health_check,
165
+ network,
166
+ nic,
167
+ use_all_nics,
168
+ gpus,
169
+ config,
170
+ render,
171
+ user,
172
+ terminal,
173
+ devices,
174
+ platform_config,
175
+ shared_memory_size,
176
+ args.uid == 0,
177
+ )
178
+
179
+
180
+ def _lookup_devices(devices: list[str]) -> list[str]:
181
+ """
182
+ Looks up matching devices in /dev and returns a list
183
+ of fully qualified device paths.
184
+
185
+ Raises exception if any devices cannot be found.
186
+ """
187
+ matched_devices = []
188
+ unmatched_devices = []
189
+
190
+ for dev in devices:
191
+ pattern = dev
192
+ if not pattern.startswith("/"):
193
+ pattern = "/dev/" + pattern
194
+ found_devices = glob(pattern)
195
+ if len(found_devices) == 0:
196
+ unmatched_devices.append(dev)
197
+ else:
198
+ matched_devices.extend(found_devices)
199
+
200
+ if len(unmatched_devices) > 0:
201
+ raise UnmatchedDeviceError(unmatched_devices)
202
+
203
+ return matched_devices
204
+
205
+
206
+ def _dependency_verification(map_name: str) -> bool:
207
+ """Check if all the dependencies are installed or not.
208
+
209
+ Args:
210
+ map_name: HAP/MAP name
211
+
212
+ Returns:
213
+ True if all dependencies are satisfied, otherwise False.
214
+ """
215
+ logger.info("Checking dependencies...")
216
+
217
+ # check for docker
218
+ prog = "docker"
219
+ logger.info(f'--> Verifying if "{prog}" is installed...\n')
220
+ if not shutil.which(prog):
221
+ logger.error(
222
+ '"%s" not installed, please install it from https://docs.docker.com/engine/install/.',
223
+ prog,
224
+ )
225
+ return False
226
+
227
+ buildx_paths = [
228
+ os.path.expandvars("$HOME/.docker/cli-plugins"),
229
+ "/usr/local/lib/docker/cli-plugins",
230
+ "/usr/local/libexec/docker/cli-plugins",
231
+ "/usr/lib/docker/cli-plugins",
232
+ "/usr/libexec/docker/cli-plugins",
233
+ ]
234
+ prog = "docker-buildx"
235
+ logger.info('--> Verifying if "%s" is installed...\n', prog)
236
+ buildx_found = False
237
+ for path in buildx_paths:
238
+ if shutil.which(os.path.join(path, prog)):
239
+ buildx_found = True
240
+
241
+ if not buildx_found:
242
+ logger.error(
243
+ '"%s" not installed, please install it from https://docs.docker.com/engine/install/.',
244
+ prog,
245
+ )
246
+ return False
247
+
248
+ # check for map image
249
+ logger.info('--> Verifying if "%s" is available...\n', map_name)
250
+ if not image_exists(map_name):
251
+ logger.error("Unable to fetch required image.")
252
+ return False
253
+
254
+ return True
255
+
256
+
257
+ def _pkg_specific_dependency_verification(pkg_info: dict) -> bool:
258
+ """Checks for any package specific dependencies.
259
+
260
+ Currently it verifies the following dependencies:
261
+ * If gpu has been requested by the application, verify that nvidia-ctk is installed.
262
+ Note: when running inside a Docker container, always assume nvidia-ctk is installed.
263
+ Args:
264
+ pkg_info: package manifest as a python dict
265
+
266
+ Returns:
267
+ True if all dependencies are satisfied, otherwise False.
268
+ """
269
+ if (
270
+ os.path.exists("/.dockerenv")
271
+ or os.environ.get("HOLOSCAN_SKIP_NVIDIA_CTK_CHECK") is not None
272
+ ):
273
+ logger.info("--> Skipping nvidia-ctk check inside Docker...\n")
274
+ return True
275
+
276
+ requested_gpus = get_requested_gpus(pkg_info)
277
+ if requested_gpus > 0:
278
+ # check for NVIDIA Container TOolkit
279
+ prog = "nvidia-ctk"
280
+ logger.info('--> Verifying if "%s" is installed...\n', prog)
281
+ if not shutil.which(prog):
282
+ logger.error(
283
+ '"%s" not installed, please install NVIDIA Container Toolkit.', prog
284
+ )
285
+ return False
286
+
287
+ logger.info('--> Verifying "%s" version...\n', prog)
288
+ output = run_cmd_output(["nvidia-ctk", "--version"], "version")
289
+ match = re.search(r"([0-9]+\.[0-9]+\.[0-9]+)", output)
290
+ min_ctk_version = "1.12.0"
291
+ recommended_ctk_version = "1.14.1"
292
+
293
+ if match is None:
294
+ logger.error(
295
+ f"Error detecting NVIDIA Container Toolkit version. "
296
+ f"Version {min_ctk_version}+ is required ({recommended_ctk_version}+ recommended)."
297
+ )
298
+ return False
299
+ elif compare_versions(min_ctk_version, match.group()) > 0:
300
+ logger.error(
301
+ f"Found '{prog}' Version {match.group()}. "
302
+ f"Version {min_ctk_version}+ is required ({recommended_ctk_version}+ recommended)."
303
+ )
304
+ return False
305
+
306
+ return True
307
+
308
+
309
+ def execute_run_command(args: Namespace):
310
+ """
311
+ Entrypoint for the Holoscan Run command.
312
+
313
+ Args:
314
+ args: Namespace object containing user arguments.
315
+
316
+ Returns:
317
+ None
318
+ """
319
+ if not _dependency_verification(args.map):
320
+ logger.error("Execution Aborted")
321
+ sys.exit(2)
322
+
323
+ try:
324
+ # Fetch application manifest from MAP
325
+ app_info, pkg_info = _fetch_map_manifest(args.map)
326
+ except Exception as e:
327
+ logger.error(f"Failed to fetch MAP manifest: {e}")
328
+ sys.exit(2)
329
+
330
+ if not _pkg_specific_dependency_verification(pkg_info):
331
+ logger.error("Execution Aborted")
332
+ sys.exit(2)
333
+
334
+ try:
335
+ # Run Holoscan Application
336
+ _run_app(args, app_info, pkg_info)
337
+ except Exception as ex:
338
+ logger.debug(ex, exc_info=True)
339
+ logger.error(f"Error executing {args.map}: {ex}")
340
+ sys.exit(2)
@@ -0,0 +1,15 @@
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 .version import execute_version_command # noqa: F401
@@ -0,0 +1,53 @@
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
+ import sys
18
+ from argparse import Namespace
19
+
20
+ from ..common.enum_types import SdkType
21
+ from ..common.sdk_utils import (
22
+ detect_holoscan_version,
23
+ detect_monaideploy_version,
24
+ detect_sdk,
25
+ )
26
+
27
+ logger = logging.getLogger("version")
28
+
29
+
30
+ def execute_version_command(args: Namespace):
31
+ print(
32
+ f"You are executing Holoscan CLI from: {os.path.dirname(os.path.abspath(sys.argv[0]))}\n"
33
+ )
34
+
35
+ try:
36
+ sdk = detect_sdk()
37
+ try:
38
+ sdk_version = detect_holoscan_version()
39
+ print(f"Holoscan SDK: {sdk_version}")
40
+ except Exception:
41
+ print("Holoscan SDK: N/A")
42
+
43
+ if sdk == SdkType.MonaiDeploy:
44
+ try:
45
+ sdk_version = detect_monaideploy_version()
46
+ print(f"MONAI Deploy App SDK: {sdk_version}")
47
+ except Exception:
48
+ print("MONAI Deploy App SDK: N/A")
49
+
50
+ except Exception as ex:
51
+ logging.error("Error executing version command.")
52
+ logger.debug(ex)
53
+ sys.exit(3)