@@ -56,15 +56,17 @@ def run_cmd(cmd):
5656 output = subprocess .check_output (cmd , stderr = subprocess .STDOUT , shell = True )
5757 # Print output even on success to show warnings
5858 if output :
59- decoded_output = output .decode ()
59+ decoded_output = output .decode ("utf-8" , errors = "replace" )
6060 if decoded_output .strip (): # Only print if not just whitespace
6161 # In parallel builds, associate output with its command for clarity
6262 # Use single print to avoid interleaving with other processes
6363 print (f"Output from: { cmd } \n { decoded_output } " )
6464 return output
6565 except subprocess .CalledProcessError as e :
6666 # Single print to avoid interleaving in parallel builds
67- print (f"Command failed with exit code { e .returncode } : { cmd } \n Command output was:\n { e .output .decode ()} " )
67+ print (
68+ f"Command failed with exit code { e .returncode } : { cmd } \n Command output was:\n { e .output .decode ('utf-8' , errors = 'replace' )} "
69+ )
6870 raise e
6971
7072
@@ -96,6 +98,38 @@ def set_msvc_env(msvc_path, sdk_path):
9698 return os .path .join (msvc_path , "bin" , "HostX64" , "x64" , "cl.exe" )
9799
98100
101+ def _find_vswhere () -> str :
102+ """Locate vswhere.exe using multiple discovery strategies.
103+
104+ Search order:
105+ 1. ``PATH`` (covers Chocolatey, Scoop, winget, or manual installs)
106+ 2. Default VS Installer location under ``%ProgramFiles(x86)%``
107+ 3. Fallback under ``%ProgramFiles%`` (32-bit Windows or older setups)
108+
109+ Returns:
110+ Absolute path to vswhere.exe, or empty string if not found.
111+ """
112+ path_result = shutil .which ("vswhere.exe" )
113+ if path_result :
114+ if verbose_cmd :
115+ print (f"Found vswhere.exe in PATH: { path_result } " )
116+ return path_result
117+
118+ candidates = [
119+ os .path .expandvars (r"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" ),
120+ os .path .expandvars (r"%ProgramFiles%\Microsoft Visual Studio\Installer\vswhere.exe" ),
121+ ]
122+ for candidate in candidates :
123+ if os .path .isfile (candidate ):
124+ if verbose_cmd :
125+ print (f"Found vswhere.exe at: { candidate } " )
126+ return candidate
127+
128+ if verbose_cmd :
129+ print ("Warning: Could not locate vswhere.exe" )
130+ return ""
131+
132+
99133def find_host_compiler () -> str :
100134 """Find the host C++ compiler.
101135
@@ -124,26 +158,48 @@ def find_host_compiler() -> str:
124158 if verbose_cmd :
125159 print ("Warning: VS environment variables set but cl.exe not found, attempting auto-configuration" )
126160
127- vswhere_path = r"%ProgramFiles(x86)%/Microsoft Visual Studio/Installer/vswhere.exe"
128- vswhere_path = os .path .expandvars (vswhere_path )
129- if not os .path .exists (vswhere_path ):
130- return "" # Signal to caller that VS not found
161+ vswhere_path = _find_vswhere ()
162+ if not vswhere_path :
163+ return "" # Signal to caller that vswhere.exe not found
131164
132- vs_path = run_cmd (f'"{ vswhere_path } " -latest -property installationPath' ).decode ().rstrip ()
165+ try :
166+ vs_path = (
167+ run_cmd (f'"{ vswhere_path } " -latest -property installationPath' )
168+ .decode ("utf-8" , errors = "replace" )
169+ .rstrip ()
170+ )
171+ except subprocess .CalledProcessError :
172+ return "" # Signal to caller that vswhere command failed
173+ if not vs_path :
174+ if verbose_cmd :
175+ print ("Warning: vswhere.exe found no Visual Studio installation" )
176+ return ""
133177 vsvars_path = os .path .join (vs_path , "VC\\ Auxiliary\\ Build\\ vcvars64.bat" )
134178
135179 if not os .path .exists (vsvars_path ):
136180 return "" # Signal to caller that VS environment script not found
137181
138- output = run_cmd (f'"{ vsvars_path } " && set' ).decode ()
182+ try :
183+ output = run_cmd (f'"{ vsvars_path } " && set' ).decode ("utf-8" , errors = "replace" )
184+ except subprocess .CalledProcessError :
185+ return "" # Signal to caller that VS environment script failed
139186
140187 for line in output .splitlines ():
141188 pair = line .split ("=" , 1 )
142189 if len (pair ) >= 2 :
143190 os .environ [pair [0 ]] = pair [1 ]
144191
145192 cl_path = shutil .which ("cl.exe" )
146- cl_version = os .environ ["VCToolsVersion" ].split ("." )
193+ if not cl_path :
194+ if verbose_cmd :
195+ print ("Warning: cl.exe not found in PATH after running vcvars64.bat" )
196+ return ""
197+ vc_tools_version = os .environ .get ("VCToolsVersion" , "" )
198+ if not vc_tools_version :
199+ if verbose_cmd :
200+ print ("Warning: VCToolsVersion not set after running vcvars64.bat" )
201+ return ""
202+ cl_version = vc_tools_version .split ("." )
147203
148204 # ensure at least VS2019 version, see list of MSVC versions here https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B
149205 cl_required_major = 14
0 commit comments