Coverage for cuda/pathfinder/_binaries/find_nvidia_binary_utility.py: 98.46%

65 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-07-29 01:38 +0000

1# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 

2# SPDX-License-Identifier: Apache-2.0 

3 

4import functools 

5import os 

6 

7from cuda.pathfinder._binaries import supported_nvidia_binaries 

8from cuda.pathfinder._utils.ctk_root_canary import CTK_ROOT_CANARY_ANCHOR_LIBNAMES 

9from cuda.pathfinder._utils.env_vars import get_cuda_path_or_home 

10from cuda.pathfinder._utils.find_sub_dirs import find_sub_dirs_all_sitepackages 

11from cuda.pathfinder._utils.platform_aware import IS_WINDOWS 

12 

13 

14class UnsupportedBinaryError(Exception): 

15 def __init__(self, utility: str) -> None: 

16 super().__init__(utility) 1F

17 self.utility = utility 1F

18 

19 def __str__(self) -> str: 

20 supported_utilities = ", ".join(supported_nvidia_binaries.SUPPORTED_BINARIES) 1F

21 return f"Binary '{self.utility}' is not supported. Supported utilities are: {supported_utilities}" 1F

22 

23 

24def _normalize_utility_name(utility_name: str) -> str: 

25 """Normalize utility name by adding .exe on Windows if needed.""" 

26 if IS_WINDOWS and not utility_name.lower().endswith((".exe", ".bat", ".cmd")): 1azwvutxqbfghicjklmdneoprs

27 return f"{utility_name}.exe" 1avbfghicjklmdneopr

28 return utility_name 1azwutxqbfghicjklmdneops

29 

30 

31def _is_executable_candidate(path: str) -> bool: 

32 if not os.path.isfile(path): 1ABCDabfghicjklmdneop

33 return False 1Aabfghicjklmdneop

34 if IS_WINDOWS: 1ABCDabcde

35 return True 1ABCabcde

36 return os.access(path, os.X_OK) 1ABCDabcde

37 

38 

39def _ctk_bin_subdirs(root: str) -> list[str]: 

40 if IS_WINDOWS: 1avutqbfghicjklmdneoprs

41 return [ 1avbfghicjklmdneopr

42 os.path.join(root, "bin", "x64"), 

43 os.path.join(root, "bin", "x86_64"), 

44 os.path.join(root, "bin"), 

45 ] 

46 return [os.path.join(root, "bin")] 1autqbfghicjklmdneops

47 

48 

49def _resolve_ctk_root_via_canary() -> str | None: 

50 from cuda.pathfinder._dynamic_libs.load_nvidia_dynamic_lib import resolve_ctk_root_via_canary 1abfghicjklmdneop

51 

52 ctk_root: str | None = resolve_ctk_root_via_canary(CTK_ROOT_CANARY_ANCHOR_LIBNAMES[0]) 1abfghicjklmdneop

53 return ctk_root 1abfghicjklmdneop

54 

55 

56def _resolve_in_trusted_dirs(normalized_name: str, dirs: list[str]) -> str | None: 

57 """Resolve ``normalized_name`` against ``dirs`` in order.""" 

58 seen: set[str] = set() 1ABGCDazwvutxqbfghicjklmdneoprsE

59 for directory in dirs: 1ABGCDazwvutxqbfghicjklmdneoprsE

60 if directory in seen: 1ABGCDawvutqbfghicjklmdneoprsE

61 continue 

62 assert directory 1ABGCDawvutqbfghicjklmdneoprsE

63 seen.add(directory) 1ABCDawvutqbfghicjklmdneoprsE

64 candidate = os.path.join(directory, normalized_name) 1ABCDawvutqbfghicjklmdneoprsE

65 if _is_executable_candidate(candidate): 1ABCDawvutqbfghicjklmdneoprsE

66 # Return an absolute path, as the docstring promises (a relative 

67 # search dir would otherwise leak a relative result). 

68 return os.path.abspath(candidate) 1ABCDawutbcdeE

69 return None 1ABDazvuxqbfghicjklmdneoprs

70 

71 

72@functools.cache 

73def find_nvidia_binary_utility(utility_name: str) -> str | None: 

74 """Locate a CUDA binary utility executable. 

75 

76 Args: 

77 utility_name (str): The name of the binary utility to find 

78 (e.g., ``"nvdisasm"``, ``"cuobjdump"``). On Windows, the ``.exe`` 

79 extension will be automatically appended if not present. The function 

80 also recognizes ``.bat`` and ``.cmd`` files on Windows. 

81 

82 Returns: 

83 str or None: Absolute path to the discovered executable, or ``None`` 

84 if the utility cannot be found. The returned path is normalized 

85 (absolute and with resolved separators). 

86 

87 Raises: 

88 UnsupportedBinaryError: If ``utility_name`` is not in the supported set 

89 (see ``SUPPORTED_BINARY_UTILITIES``). 

90 

91 Search order: 

92 1. **NVIDIA Python wheels** 

93 

94 - Scan installed distributions (``site-packages``) for binary layouts 

95 shipped in NVIDIA wheels (e.g., ``cuda-nvcc``). 

96 

97 2. **Conda environments** 

98 

99 - Check Conda-style installation prefixes via ``CONDA_PREFIX`` 

100 environment variable, which use platform-specific bin directory 

101 layouts (``Library/bin`` on Windows, ``bin`` on Linux). 

102 

103 3. **CUDA Toolkit environment variables** 

104 

105 - Use ``CUDA_HOME`` or ``CUDA_PATH`` (in that order), searching 

106 ``bin/x64``, ``bin/x86_64``, and ``bin`` subdirectories on Windows, 

107 or just ``bin`` on Linux. 

108 

109 4. **CTK-root canary fallback** 

110 

111 - Only when steps 1-3 miss: resolve the ``cudart`` library through the 

112 OS dynamic loader, derive the CUDA Toolkit root from it, and search 

113 that root's bin layout. 

114 

115 Note: 

116 Results are cached using ``@functools.cache`` for performance. The cache 

117 persists for the lifetime of the process. 

118 

119 On Windows, executables are identified by their file extensions 

120 (``.exe``, ``.bat``, ``.cmd``). On Unix-like systems, executables 

121 are identified by the ``X_OK`` (execute) permission bit. 

122 

123 Lookup is restricted to the trusted directories and the canary-derived 

124 CTK root listed above. 

125 

126 Example: 

127 >>> from cuda.pathfinder import find_nvidia_binary_utility 

128 >>> nvdisasm = find_nvidia_binary_utility("nvdisasm") 

129 >>> if nvdisasm: 

130 ... print(f"Found nvdisasm at: {nvdisasm}") 

131 """ 

132 if utility_name not in supported_nvidia_binaries.SUPPORTED_BINARIES: 1azwvutxqbfghicjklmdneoprsF

133 raise UnsupportedBinaryError(utility_name) 1F

134 

135 # 1. Search in site-packages (NVIDIA wheels) 

136 candidate_dirs = supported_nvidia_binaries.SITE_PACKAGES_BINDIRS.get(utility_name, ()) 1azwvutxqbfghicjklmdneoprs

137 dirs = [] 1azwvutxqbfghicjklmdneoprs

138 

139 for sub_dir in candidate_dirs: 1azwvutxqbfghicjklmdneoprs

140 dirs.extend(find_sub_dirs_all_sitepackages(sub_dir.split(os.sep))) 1atxqbfghicjklmdneopr

141 

142 # 2. Search in Conda environment 

143 if (conda_prefix := os.environ.get("CONDA_PREFIX")) is not None: 1azwvutxqbfghicjklmdneoprs

144 if IS_WINDOWS: 1wtqrs

145 dirs.append(os.path.join(conda_prefix, "Library", "bin")) 1r

146 else: 

147 dirs.append(os.path.join(conda_prefix, "bin")) 1wtqs

148 

149 # 3. Search in CUDA Toolkit (CUDA_HOME/CUDA_PATH) 

150 if (cuda_home := get_cuda_path_or_home()) is not None: 1azwvutxqbfghicjklmdneoprs

151 dirs.extend(_ctk_bin_subdirs(cuda_home)) 1atqbfghicjklmdneoprs

152 

153 normalized_name = _normalize_utility_name(utility_name) 1azwvutxqbfghicjklmdneoprs

154 found = _resolve_in_trusted_dirs(normalized_name, dirs) 1azwvutxqbfghicjklmdneoprs

155 if found is not None: 1azwvutxqbfghicjklmdneoprs

156 return found 1awtbcde

157 

158 # 4. CTK-root canary fallback. 

159 ctk_root = _resolve_ctk_root_via_canary() 1azvuxqbfghicjklmdneoprs

160 if ctk_root is not None: 1azvuxqbfghicjklmdneoprs

161 return _resolve_in_trusted_dirs(normalized_name, _ctk_bin_subdirs(ctk_root)) 1avubfghicjklmdneop

162 return None 1azxqfghijklmnoprs