Merge pull request #4657 from mwichmann/feature/vars-defaulted
[scons.git] / SCons / Tool / MSCommon / vs.py
blob34fb670b5fc98f58051965ce86652c1ddf6a1274
1 # MIT License
3 # Copyright The SCons Foundation
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
17 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
18 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 """
25 MS Compilers: detect Visual Studio and/or Visual C/C++
26 """
28 import os
30 import SCons.Errors
31 import SCons.Util
33 from .common import (
34 debug,
35 get_output,
36 is_win64,
37 normalize_env,
38 parse_output,
39 read_reg,
42 from .vc import (
43 find_vc_pdir,
44 get_msvc_version_numeric,
45 reset_installed_vcs,
46 vswhere_freeze_env,
50 # Visual Studio express version policy when unqualified version is not installed:
51 # True: use express version for unqualified version (e.g., use 12.0Exp for 12.0)
52 # False: do not use express version for unqualified version
53 _VSEXPRESS_USE_VERSTR = True
56 class VisualStudio:
57 """
58 An abstract base class for trying to find installed versions of
59 Visual Studio.
60 """
61 def __init__(self, version, **kw) -> None:
62 self.version = version
63 self.verstr = get_msvc_version_numeric(version)
64 self.vernum = float(self.verstr)
65 self.is_express = True if self.verstr != self.version else False
66 kw['vc_version'] = kw.get('vc_version', version)
67 kw['sdk_version'] = kw.get('sdk_version', version)
68 self.__dict__.update(kw)
69 self._cache = {}
71 def find_batch_file(self):
72 vs_dir = self.get_vs_dir()
73 if not vs_dir:
74 debug('no vs_dir')
75 return None
76 batch_file = os.path.join(vs_dir, self.batch_file_path)
77 batch_file = os.path.normpath(batch_file)
78 if not os.path.isfile(batch_file):
79 debug('%s not on file system', batch_file)
80 return None
81 return batch_file
83 def find_vs_dir_by_vc(self, env):
84 dir = find_vc_pdir(self.vc_version, env)
85 if not dir:
86 debug('no installed VC %s', self.vc_version)
87 return None
88 return os.path.abspath(os.path.join(dir, os.pardir))
90 def find_vs_dir_by_reg(self, env):
91 root = 'Software\\'
93 if is_win64():
94 root = root + 'Wow6432Node\\'
95 for key in self.hkeys:
96 if key=='use_dir':
97 return self.find_vs_dir_by_vc(env)
98 key = root + key
99 try:
100 comps = read_reg(key)
101 except OSError:
102 debug('no VS registry key %s', repr(key))
103 else:
104 debug('found VS in registry: %s', comps)
105 return comps
106 return None
108 def find_vs_dir(self, env):
109 """ Can use registry or location of VC to find vs dir
110 First try to find by registry, and if that fails find via VC dir
113 vs_dir = self.find_vs_dir_by_reg(env)
114 if not vs_dir:
115 vs_dir = self.find_vs_dir_by_vc(env)
116 debug('found VS in %s', str(vs_dir))
117 return vs_dir
119 def find_executable(self, env):
120 vs_dir = self.get_vs_dir(env)
121 if not vs_dir:
122 debug('no vs_dir (%s)', vs_dir)
123 return None
124 executable = os.path.join(vs_dir, self.executable_path)
125 executable = os.path.normpath(executable)
126 if not os.path.isfile(executable):
127 debug('%s not on file system', executable)
128 return None
129 return executable
131 def get_batch_file(self):
132 try:
133 return self._cache['batch_file']
134 except KeyError:
135 batch_file = self.find_batch_file()
136 self._cache['batch_file'] = batch_file
137 return batch_file
139 def get_executable(self, env=None):
140 try:
141 debug('using cache:%s', self._cache['executable'])
142 return self._cache['executable']
143 except KeyError:
144 executable = self.find_executable(env)
145 self._cache['executable'] = executable
146 debug('not in cache:%s', executable)
147 return executable
149 def get_vs_dir(self, env=None):
150 try:
151 return self._cache['vs_dir']
152 except KeyError:
153 vs_dir = self.find_vs_dir(env)
154 self._cache['vs_dir'] = vs_dir
155 return vs_dir
157 def get_supported_arch(self):
158 try:
159 return self._cache['supported_arch']
160 except KeyError:
161 # RDEVE: for the time being use hardcoded lists
162 # supported_arch = self.find_supported_arch()
163 self._cache['supported_arch'] = self.supported_arch
164 return self.supported_arch
166 def reset(self) -> None:
167 self._cache = {}
169 # The list of supported Visual Studio versions we know how to detect.
171 # How to look for .bat file ?
172 # - VS 2008 Express (x86):
173 # * from registry key productdir, gives the full path to vsvarsall.bat. In
174 # HKEY_LOCAL_MACHINE):
175 # Software\Microsoft\VCEpress\9.0\Setup\VC\productdir
176 # * from environmnent variable VS90COMNTOOLS: the path is then ..\..\VC
177 # relatively to the path given by the variable.
179 # - VS 2008 Express (WoW6432: 32 bits on windows x64):
180 # Software\Wow6432Node\Microsoft\VCEpress\9.0\Setup\VC\productdir
182 # - VS 2005 Express (x86):
183 # * from registry key productdir, gives the full path to vsvarsall.bat. In
184 # HKEY_LOCAL_MACHINE):
185 # Software\Microsoft\VCEpress\8.0\Setup\VC\productdir
186 # * from environmnent variable VS80COMNTOOLS: the path is then ..\..\VC
187 # relatively to the path given by the variable.
189 # - VS 2005 Express (WoW6432: 32 bits on windows x64): does not seem to have a
190 # productdir ?
192 # - VS 2003 .Net (pro edition ? x86):
193 # * from registry key productdir. The path is then ..\Common7\Tools\
194 # relatively to the key. The key is in HKEY_LOCAL_MACHINE):
195 # Software\Microsoft\VisualStudio\7.1\Setup\VC\productdir
196 # * from environmnent variable VS71COMNTOOLS: the path is the full path to
197 # vsvars32.bat
199 # - VS 98 (VS 6):
200 # * from registry key productdir. The path is then Bin
201 # relatively to the key. The key is in HKEY_LOCAL_MACHINE):
202 # Software\Microsoft\VisualStudio\6.0\Setup\VC98\productdir
204 # The first version found in the list is the one used by default if
205 # there are multiple versions installed. Barring good reasons to
206 # the contrary, this means we should list versions from most recent
207 # to oldest. Pro versions get listed before Express versions on the
208 # assumption that, by default, you'd rather use the version you paid
209 # good money for in preference to whatever Microsoft makes available
210 # for free.
212 # If you update this list, update _VCVER and _VCVER_TO_PRODUCT_DIR in
213 # Tool/MSCommon/vc.py, and the MSVC_VERSION documentation in Tool/msvc.xml.
215 SupportedVSList = [
216 # Visual Studio 2022
217 VisualStudio('14.3',
218 vc_version='14.3',
219 sdk_version='10.0A',
220 hkeys=[],
221 common_tools_var='VS170COMNTOOLS',
222 executable_path=r'Common7\IDE\devenv.com',
223 # should be a fallback, prefer use vswhere installationPath
224 batch_file_path=r'Common7\Tools\VsDevCmd.bat',
225 supported_arch=['x86', 'amd64', "arm", 'arm64'],
228 # Visual Studio 2019
229 VisualStudio('14.2',
230 vc_version='14.2',
231 sdk_version='10.0A',
232 hkeys=[],
233 common_tools_var='VS160COMNTOOLS',
234 executable_path=r'Common7\IDE\devenv.com',
235 # should be a fallback, prefer use vswhere installationPath
236 batch_file_path=r'Common7\Tools\VsDevCmd.bat',
237 supported_arch=['x86', 'amd64', "arm", 'arm64'],
240 # Visual Studio 2017
241 VisualStudio('14.1',
242 vc_version='14.1',
243 sdk_version='10.0A',
244 hkeys=[],
245 common_tools_var='VS150COMNTOOLS',
246 executable_path=r'Common7\IDE\devenv.com',
247 # should be a fallback, prefer use vswhere installationPath
248 batch_file_path=r'Common7\Tools\VsDevCmd.bat',
249 supported_arch=['x86', 'amd64', "arm", 'arm64'],
252 # Visual C++ 2017 Express Edition (for Desktop)
253 VisualStudio('14.1Exp',
254 vc_version='14.1',
255 sdk_version='10.0A',
256 hkeys=[],
257 common_tools_var='VS150COMNTOOLS',
258 executable_path=r'Common7\IDE\WDExpress.exe',
259 # should be a fallback, prefer use vswhere installationPath
260 batch_file_path=r'Common7\Tools\VsDevCmd.bat',
261 supported_arch=['x86', 'amd64', "arm", 'arm64'],
264 # Visual Studio 2015
265 VisualStudio('14.0',
266 vc_version='14.0',
267 sdk_version='10.0',
268 hkeys=[r'Microsoft\VisualStudio\14.0\Setup\VS\ProductDir'],
269 common_tools_var='VS140COMNTOOLS',
270 executable_path=r'Common7\IDE\devenv.com',
271 batch_file_path=r'Common7\Tools\vsvars32.bat',
272 supported_arch=['x86', 'amd64', "arm"],
275 # Visual C++ 2015 Express Edition (for Desktop)
276 VisualStudio('14.0Exp',
277 vc_version='14.0',
278 sdk_version='10.0A',
279 hkeys=[r'Microsoft\VisualStudio\14.0\Setup\VS\ProductDir'],
280 common_tools_var='VS140COMNTOOLS',
281 executable_path=r'Common7\IDE\WDExpress.exe',
282 batch_file_path=r'Common7\Tools\vsvars32.bat',
283 supported_arch=['x86', 'amd64', "arm"],
286 # Visual Studio 2013
287 VisualStudio('12.0',
288 vc_version='12.0',
289 sdk_version='8.1A',
290 hkeys=[r'Microsoft\VisualStudio\12.0\Setup\VS\ProductDir'],
291 common_tools_var='VS120COMNTOOLS',
292 executable_path=r'Common7\IDE\devenv.com',
293 batch_file_path=r'Common7\Tools\vsvars32.bat',
294 supported_arch=['x86', 'amd64'],
297 # Visual C++ 2013 Express Edition (for Desktop)
298 VisualStudio('12.0Exp',
299 vc_version='12.0',
300 sdk_version='8.1A',
301 hkeys=[r'Microsoft\VisualStudio\12.0\Setup\VS\ProductDir'],
302 common_tools_var='VS120COMNTOOLS',
303 executable_path=r'Common7\IDE\WDExpress.exe',
304 batch_file_path=r'Common7\Tools\vsvars32.bat',
305 supported_arch=['x86', 'amd64'],
308 # Visual Studio 2012
309 VisualStudio('11.0',
310 sdk_version='8.0A',
311 hkeys=[r'Microsoft\VisualStudio\11.0\Setup\VS\ProductDir'],
312 common_tools_var='VS110COMNTOOLS',
313 executable_path=r'Common7\IDE\devenv.com',
314 batch_file_path=r'Common7\Tools\vsvars32.bat',
315 supported_arch=['x86', 'amd64'],
318 # Visual C++ 2012 Express Edition (for Desktop)
319 VisualStudio('11.0Exp',
320 vc_version='11.0',
321 sdk_version='8.0A',
322 hkeys=[r'Microsoft\VisualStudio\11.0\Setup\VS\ProductDir'],
323 common_tools_var='VS110COMNTOOLS',
324 executable_path=r'Common7\IDE\WDExpress.exe',
325 batch_file_path=r'Common7\Tools\vsvars32.bat',
326 supported_arch=['x86', 'amd64'],
329 # Visual Studio 2010
330 VisualStudio('10.0',
331 sdk_version='7.0A',
332 hkeys=[r'Microsoft\VisualStudio\10.0\Setup\VS\ProductDir'],
333 common_tools_var='VS100COMNTOOLS',
334 executable_path=r'Common7\IDE\devenv.com',
335 batch_file_path=r'Common7\Tools\vsvars32.bat',
336 supported_arch=['x86', 'amd64'],
339 # Visual C++ 2010 Express Edition
340 VisualStudio('10.0Exp',
341 vc_version='10.0',
342 sdk_version='7.0A',
343 hkeys=[r'Microsoft\VCExpress\10.0\Setup\VS\ProductDir'],
344 common_tools_var='VS100COMNTOOLS',
345 executable_path=r'Common7\IDE\VCExpress.exe',
346 batch_file_path=r'Common7\Tools\vsvars32.bat',
347 supported_arch=['x86'],
350 # Visual Studio 2008
351 VisualStudio('9.0',
352 sdk_version='6.0A',
353 hkeys=[r'Microsoft\VisualStudio\9.0\Setup\VS\ProductDir'],
354 common_tools_var='VS90COMNTOOLS',
355 executable_path=r'Common7\IDE\devenv.com',
356 batch_file_path=r'Common7\Tools\vsvars32.bat',
357 supported_arch=['x86', 'amd64'],
360 # Visual C++ 2008 Express Edition
361 VisualStudio('9.0Exp',
362 vc_version='9.0',
363 sdk_version='6.0A',
364 hkeys=[r'Microsoft\VCExpress\9.0\Setup\VS\ProductDir'],
365 common_tools_var='VS90COMNTOOLS',
366 executable_path=r'Common7\IDE\VCExpress.exe',
367 batch_file_path=r'Common7\Tools\vsvars32.bat',
368 supported_arch=['x86'],
371 # Visual Studio 2005
372 VisualStudio('8.0',
373 sdk_version='6.0A',
374 hkeys=[r'Microsoft\VisualStudio\8.0\Setup\VS\ProductDir'],
375 common_tools_var='VS80COMNTOOLS',
376 executable_path=r'Common7\IDE\devenv.com',
377 batch_file_path=r'Common7\Tools\vsvars32.bat',
378 default_dirname='Microsoft Visual Studio 8',
379 supported_arch=['x86', 'amd64'],
382 # Visual C++ 2005 Express Edition
383 VisualStudio('8.0Exp',
384 vc_version='8.0Exp',
385 sdk_version='6.0A',
386 hkeys=[r'Microsoft\VCExpress\8.0\Setup\VS\ProductDir'],
387 common_tools_var='VS80COMNTOOLS',
388 executable_path=r'Common7\IDE\VCExpress.exe',
389 batch_file_path=r'Common7\Tools\vsvars32.bat',
390 default_dirname='Microsoft Visual Studio 8',
391 supported_arch=['x86'],
394 # Visual Studio .NET 2003
395 VisualStudio('7.1',
396 sdk_version='6.0',
397 hkeys=[r'Microsoft\VisualStudio\7.1\Setup\VS\ProductDir'],
398 common_tools_var='VS71COMNTOOLS',
399 executable_path=r'Common7\IDE\devenv.com',
400 batch_file_path=r'Common7\Tools\vsvars32.bat',
401 default_dirname='Microsoft Visual Studio .NET 2003',
402 supported_arch=['x86'],
405 # Visual Studio .NET
406 VisualStudio('7.0',
407 sdk_version='2003R2',
408 hkeys=[r'Microsoft\VisualStudio\7.0\Setup\VS\ProductDir'],
409 common_tools_var='VSCOMNTOOLS',
410 executable_path=r'Common7\IDE\devenv.com',
411 batch_file_path=r'Common7\Tools\vsvars32.bat',
412 default_dirname='Microsoft Visual Studio .NET',
413 supported_arch=['x86'],
416 # Visual Studio 6.0
417 VisualStudio('6.0',
418 sdk_version='2003R1',
419 hkeys=[r'Microsoft\VisualStudio\6.0\Setup\Microsoft Visual Studio\ProductDir',
420 'use_dir'],
421 common_tools_var='MSDevDir',
422 executable_path=r'Common\MSDev98\Bin\MSDEV.COM',
423 batch_file_path=r'Common7\Tools\vsvars32.bat',
424 default_dirname='Microsoft Visual Studio',
425 supported_arch=['x86'],
429 SupportedVSMap = {}
430 for vs in SupportedVSList:
431 SupportedVSMap[vs.version] = vs
434 # Finding installed versions of Visual Studio isn't cheap, because it
435 # goes not only to the registry but also to the disk to sanity-check
436 # that there is, in fact, a Visual Studio directory there and that the
437 # registry entry isn't just stale. Find this information once, when
438 # requested, and cache it.
440 InstalledVSList = None
441 InstalledVSMap = None
443 def get_installed_visual_studios(env=None):
444 global InstalledVSList
445 global InstalledVSMap
446 vswhere_freeze_env(env)
447 if InstalledVSList is None:
448 InstalledVSList = []
449 InstalledVSMap = {}
450 for vs in SupportedVSList:
451 debug('trying to find VS %s', vs.version)
452 if vs.get_executable(env):
453 debug('found VS %s', vs.version)
454 InstalledVSList.append(vs)
455 if vs.is_express and vs.verstr not in InstalledVSMap:
456 if _VSEXPRESS_USE_VERSTR:
457 InstalledVSMap[vs.verstr] = vs
458 InstalledVSMap[vs.version] = vs
459 return InstalledVSList
461 def _get_installed_vss(env=None):
462 get_installed_visual_studios(env)
463 versions = list(InstalledVSMap.keys())
464 return versions
466 def reset_installed_visual_studios() -> None:
467 global InstalledVSList
468 global InstalledVSMap
469 debug('')
470 InstalledVSList = None
471 InstalledVSMap = None
472 for vs in SupportedVSList:
473 vs.reset()
475 # Need to clear installed VC's as well as they are used in finding
476 # installed VS's
477 reset_installed_vcs()
480 # We may be asked to update multiple construction environments with
481 # SDK information. When doing this, we check on-disk for whether
482 # the SDK has 'mfc' and 'atl' subdirectories. Since going to disk
483 # is expensive, cache results by directory.
485 #SDKEnvironmentUpdates = {}
487 #def set_sdk_by_directory(env, sdk_dir):
488 # global SDKEnvironmentUpdates
489 # try:
490 # env_tuple_list = SDKEnvironmentUpdates[sdk_dir]
491 # except KeyError:
492 # env_tuple_list = []
493 # SDKEnvironmentUpdates[sdk_dir] = env_tuple_list
495 # include_path = os.path.join(sdk_dir, 'include')
496 # mfc_path = os.path.join(include_path, 'mfc')
497 # atl_path = os.path.join(include_path, 'atl')
499 # if os.path.exists(mfc_path):
500 # env_tuple_list.append(('INCLUDE', mfc_path))
501 # if os.path.exists(atl_path):
502 # env_tuple_list.append(('INCLUDE', atl_path))
503 # env_tuple_list.append(('INCLUDE', include_path))
505 # env_tuple_list.append(('LIB', os.path.join(sdk_dir, 'lib')))
506 # env_tuple_list.append(('LIBPATH', os.path.join(sdk_dir, 'lib')))
507 # env_tuple_list.append(('PATH', os.path.join(sdk_dir, 'bin')))
509 # for variable, directory in env_tuple_list:
510 # env.PrependENVPath(variable, directory)
512 def msvs_exists(env=None) -> bool:
513 return len(get_installed_visual_studios(env)) > 0
515 def get_vs_by_version(msvs, env=None):
516 global InstalledVSMap
517 global SupportedVSMap
519 debug('called')
520 if msvs not in SupportedVSMap:
521 msg = "Visual Studio version %s is not supported" % repr(msvs)
522 raise SCons.Errors.UserError(msg)
523 get_installed_visual_studios(env)
524 vs = InstalledVSMap.get(msvs)
525 debug('InstalledVSMap:%s', InstalledVSMap)
526 debug('found vs:%s', vs)
527 # Some check like this would let us provide a useful error message
528 # if they try to set a Visual Studio version that's not installed.
529 # However, we also want to be able to run tests (like the unit
530 # tests) on systems that don't, or won't ever, have it installed.
531 # It might be worth resurrecting this, with some configurable
532 # setting that the tests can use to bypass the check.
533 #if not vs:
534 # msg = "Visual Studio version %s is not installed" % repr(msvs)
535 # raise SCons.Errors.UserError, msg
536 return vs
538 def get_default_version(env):
539 """Returns the default version string to use for MSVS.
541 If no version was requested by the user through the MSVS environment
542 variable, query all the available visual studios through
543 get_installed_visual_studios, and take the highest one.
545 Return
546 ------
547 version: str
548 the default version.
550 if 'MSVS' not in env or not SCons.Util.is_Dict(env['MSVS']):
551 # get all versions, and remember them for speed later
552 versions = _get_installed_vss(env)
553 env['MSVS'] = {'VERSIONS' : versions}
554 else:
555 versions = env['MSVS'].get('VERSIONS', [])
557 if 'MSVS_VERSION' not in env:
558 if versions:
559 env['MSVS_VERSION'] = versions[0] #use highest version by default
560 else:
561 debug('WARNING: no installed versions found, '
562 'using first in SupportedVSList (%s)',
563 SupportedVSList[0].version)
564 env['MSVS_VERSION'] = SupportedVSList[0].version
566 env['MSVS']['VERSION'] = env['MSVS_VERSION']
568 return env['MSVS_VERSION']
570 def get_default_arch(env):
571 """Return the default arch to use for MSVS
573 if no version was requested by the user through the MSVS_ARCH environment
574 variable, select x86
576 Return
577 ------
578 arch: str
580 arch = env.get('MSVS_ARCH', 'x86')
582 msvs = InstalledVSMap.get(env['MSVS_VERSION'])
584 if not msvs:
585 arch = 'x86'
586 elif arch not in msvs.get_supported_arch():
587 fmt = "Visual Studio version %s does not support architecture %s"
588 raise SCons.Errors.UserError(fmt % (env['MSVS_VERSION'], arch))
590 return arch
592 def merge_default_version(env) -> None:
593 version = get_default_version(env)
594 arch = get_default_arch(env)
596 # TODO: refers to versions and arch which aren't defined; called nowhere. Drop?
597 def msvs_setup_env(env) -> None:
598 msvs = get_vs_by_version(version, env)
599 if msvs is None:
600 return
601 batfilename = msvs.get_batch_file()
603 # XXX: I think this is broken. This will silently set a bogus tool instead
604 # of failing, but there is no other way with the current scons tool
605 # framework
606 if batfilename is not None:
608 vars = ('LIB', 'LIBPATH', 'PATH', 'INCLUDE')
610 msvs_list = get_installed_visual_studios(env)
611 vscommonvarnames = [vs.common_tools_var for vs in msvs_list]
612 save_ENV = env['ENV']
613 nenv = normalize_env(env['ENV'],
614 ['COMSPEC'] + vscommonvarnames,
615 force=True)
616 try:
617 output = get_output(batfilename, arch, env=nenv)
618 finally:
619 env['ENV'] = save_ENV
620 vars = parse_output(output, vars)
622 for k, v in vars.items():
623 env.PrependENVPath(k, v, delete_existing=1)
625 def query_versions(env=None):
626 """Query the system to get available versions of VS. A version is
627 considered when a batfile is found."""
628 versions = _get_installed_vss(env)
629 return versions
631 # Local Variables:
632 # tab-width:4
633 # indent-tabs-mode:nil
634 # End:
635 # vim: set expandtab tabstop=4 shiftwidth=4: