1077931e2c99ff8861306c32acb64059ff747265
[scons.git] / src / engine / SCons / Tool / msvc.py
1 """engine.SCons.Tool.msvc
2
3 Tool-specific initialization for Microsoft Visual C/C++.
4
5 There normally shouldn't be any need to import this module directly.
6 It will usually be imported through the generic SCons.Tool.Tool()
7 selection method.
8
9 """
10
11 #
12 # __COPYRIGHT__
13 #
14 # Permission is hereby granted, free of charge, to any person obtaining
15 # a copy of this software and associated documentation files (the
16 # "Software"), to deal in the Software without restriction, including
17 # without limitation the rights to use, copy, modify, merge, publish,
18 # distribute, sublicense, and/or sell copies of the Software, and to
19 # permit persons to whom the Software is furnished to do so, subject to
20 # the following conditions:
21 #
22 # The above copyright notice and this permission notice shall be included
23 # in all copies or substantial portions of the Software.
24 #
25 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
26 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
27 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 #
33
34 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
35
36 import os.path
37 import re
38 import string
39
40 import SCons.Action
41 import SCons.Builder
42 import SCons.Errors
43 import SCons.Platform.win32
44 import SCons.Tool
45 import SCons.Tool.msvs
46 import SCons.Util
47 import SCons.Warnings
48
49 CSuffixes = ['.c', '.C']
50 CXXSuffixes = ['.cc', '.cpp', '.cxx', '.c++', '.C++']
51
52 def _parse_msvc7_overrides(version):
53     """ Parse any overridden defaults for MSVS directory locations in MSVS .NET. """
54
55     # First, we get the shell folder for this user:
56     if not SCons.Util.can_read_reg:
57         raise SCons.Errors.InternalError, "No Windows registry module was found"
58
59     comps = ""
60     try:
61         (comps, t) = SCons.Util.RegGetValue(SCons.Util.HKEY_CURRENT_USER,
62                                             r'Software\Microsoft\Windows\CurrentVersion' +\
63                                             r'\Explorer\Shell Folders\Local AppData')
64     except SCons.Util.RegError:
65         raise SCons.Errors.InternalError, "The Local AppData directory was not found in the registry."
66
67     comps = comps + '\\Microsoft\\VisualStudio\\' + version + '\\VCComponents.dat'
68     dirs = {}
69
70     if os.path.exists(comps):
71         # now we parse the directories from this file, if it exists.
72         # We only look for entries after: [VC\VC_OBJECTS_PLATFORM_INFO\Win32\Directories],
73         # since this file could contain a number of things...
74         lines = None
75         try:
76             import codecs
77         except ImportError:
78             pass
79         else:
80             try:
81                 f = codecs.open(comps, 'r', 'utf16')
82                 encoder = codecs.getencoder('ascii')
83             except LookupError:
84                 lines = codecs.open(comps, 'r', 'utf8').readlines()
85             else:
86                 lines = map(lambda l, e=encoder: e(l)[0], f.readlines())
87         if lines is None:
88             lines = open(comps, 'r').readlines()
89             
90         found = 0
91         for line in lines:
92             line.strip()
93             if line.find(r'[VC\VC_OBJECTS_PLATFORM_INFO\Win32\Directories]') >= 0:
94                 found = 1
95             elif line == '' or line[:1] == '[':
96                 found = 0
97             elif found == 1:
98                 kv = line.split('=', 1)
99                 if len(kv) == 2:
100                     (key, val) = kv
101                 key = key.replace(' Dirs','')
102                 dirs[key.upper()] = val
103         f.close()
104     else:
105         # since the file didn't exist, we have only the defaults in
106         # the registry to work with.
107         try:
108             K = 'SOFTWARE\\Microsoft\\VisualStudio\\' + version
109             K = K + r'\VC\VC_OBJECTS_PLATFORM_INFO\Win32\Directories'
110             k = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE,K)
111             i = 0
112             while 1:
113                 try:
114                     (key,val,t) = SCons.Util.RegEnumValue(k,i)
115                     key = key.replace(' Dirs','')
116                     dirs[key.upper()] = val
117                     i = i + 1
118                 except SCons.Util.RegError:
119                     break
120         except SCons.Util.RegError:
121             # if we got here, then we didn't find the registry entries:
122             raise SCons.Errors.InternalError, "Unable to find MSVC paths in the registry."
123     return dirs
124
125 def _get_msvc7_path(path, version, platform):
126     """
127     Get Visual Studio directories from version 7 (MSVS .NET)
128     (it has a different registry structure than versions before it)
129     """
130     # first, look for a customization of the default values in the
131     # registry: These are sometimes stored in the Local Settings area
132     # for Visual Studio, in a file, so we have to parse it.
133     dirs = _parse_msvc7_overrides(version)
134
135     if dirs.has_key(path):
136         p = dirs[path]
137     else:
138         raise SCons.Errors.InternalError, "Unable to retrieve the %s path from MS VC++."%path
139
140     # collect some useful information for later expansions...
141     paths = SCons.Tool.msvs.get_msvs_install_dirs(version)
142
143     # expand the directory path variables that we support.  If there
144     # is a variable we don't support, then replace that entry with
145     # "---Unknown Location VSInstallDir---" or something similar, to clue
146     # people in that we didn't find something, and so env expansion doesn't
147     # do weird things with the $(xxx)'s
148     s = re.compile('\$\(([a-zA-Z0-9_]+?)\)')
149
150     def repl(match, paths=paths):
151         key = string.upper(match.group(1))
152         if paths.has_key(key):
153             return paths[key]
154         else:
155             return '---Unknown Location %s---' % match.group()
156
157     rv = []
158     for entry in p.split(os.pathsep):
159         entry = s.sub(repl,entry).rstrip('\n\r')
160         rv.append(entry)
161
162     return string.join(rv,os.pathsep)
163
164 def get_msvc_path (path, version, platform='x86'):
165     """
166     Get a list of visualstudio directories (include, lib or path).  Return
167     a string delimited by ';'. An exception will be raised if unable to
168     access the registry or appropriate registry keys not found.
169     """
170
171     if not SCons.Util.can_read_reg:
172         raise SCons.Errors.InternalError, "No Windows registry module was found"
173
174     # normalize the case for comparisons (since the registry is case
175     # insensitive)
176     path = string.upper(path)
177
178     if path=='LIB':
179         path= 'LIBRARY'
180
181     if float(version) >= 7.0:
182         return _get_msvc7_path(path, version, platform)
183
184     path = string.upper(path + ' Dirs')
185     K = ('Software\\Microsoft\\Devstudio\\%s\\' +
186          'Build System\\Components\\Platforms\\Win32 (%s)\\Directories') % \
187         (version,platform)
188     for base in (SCons.Util.HKEY_CURRENT_USER,
189                  SCons.Util.HKEY_LOCAL_MACHINE):
190         try:
191             k = SCons.Util.RegOpenKeyEx(base,K)
192             i = 0
193             while 1:
194                 try:
195                     (p,v,t) = SCons.Util.RegEnumValue(k,i)
196                     if string.upper(p) == path:
197                         return v
198                     i = i + 1
199                 except SCons.Util.RegError:
200                     break
201         except SCons.Util.RegError:
202             pass
203
204     # if we got here, then we didn't find the registry entries:
205     raise SCons.Errors.InternalError, "The %s path was not found in the registry."%path
206
207 def _get_msvc6_default_paths(version, use_mfc_dirs):
208     """Return a 3-tuple of (INCLUDE, LIB, PATH) as the values of those
209     three environment variables that should be set in order to execute
210     the MSVC 6.0 tools properly, if the information wasn't available
211     from the registry."""
212     MVSdir = None
213     paths = {}
214     exe_path = ''
215     lib_path = ''
216     include_path = ''
217     try:
218         paths = SCons.Tool.msvs.get_msvs_install_dirs(version)
219         MVSdir = paths['VSINSTALLDIR']
220     except (SCons.Util.RegError, SCons.Errors.InternalError, KeyError):
221         if os.environ.has_key('MSDEVDIR'):
222             MVSdir = os.path.normpath(os.path.join(os.environ['MSDEVDIR'],'..','..'))
223         else:
224             MVSdir = r'C:\Program Files\Microsoft Visual Studio'
225     if MVSdir:
226         if SCons.Util.can_read_reg and paths.has_key('VCINSTALLDIR'):
227             MVSVCdir = paths['VCINSTALLDIR']
228         else:
229             MVSVCdir = os.path.join(MVSdir,'VC98')
230
231         MVSCommondir = r'%s\Common' % MVSdir
232         if use_mfc_dirs:
233             mfc_include_ = r'%s\ATL\include;%s\MFC\include;' % (MVSVCdir, MVSVCdir)
234             mfc_lib_ = r'%s\MFC\lib;' % MVSVCdir
235         else:
236             mfc_include_ = ''
237             mfc_lib_ = ''
238         include_path = r'%s%s\include' % (mfc_include_, MVSVCdir)
239         lib_path = r'%s%s\lib' % (mfc_lib_, MVSVCdir)
240
241         if os.environ.has_key('OS') and os.environ['OS'] == "Windows_NT":
242             osdir = 'WINNT'
243         else:
244             osdir = 'WIN95'
245
246         exe_path = r'%s\tools\%s;%s\MSDev98\bin;%s\tools;%s\bin' % (MVSCommondir, osdir, MVSCommondir,  MVSCommondir, MVSVCdir)
247     return (include_path, lib_path, exe_path)
248
249 def _get_msvc7_default_paths(version, use_mfc_dirs):
250     """Return a 3-tuple of (INCLUDE, LIB, PATH) as the values of those
251     three environment variables that should be set in order to execute
252     the MSVC .NET tools properly, if the information wasn't available
253     from the registry."""
254
255     MVSdir = None
256     paths = {}
257     exe_path = ''
258     lib_path = ''
259     include_path = ''
260     try:
261         paths = SCons.Tool.msvs.get_msvs_install_dirs(version)
262         MVSdir = paths['VSINSTALLDIR']
263     except (KeyError, SCons.Util.RegError, SCons.Errors.InternalError):
264         if os.environ.has_key('VSCOMNTOOLS'):
265             MVSdir = os.path.normpath(os.path.join(os.environ['VSCOMNTOOLS'],'..','..'))
266         else:
267             # last resort -- default install location
268             MVSdir = r'C:\Program Files\Microsoft Visual Studio .NET'
269
270     if MVSdir:
271         if SCons.Util.can_read_reg and paths.has_key('VCINSTALLDIR'):
272             MVSVCdir = paths['VCINSTALLDIR']
273         else:
274             MVSVCdir = os.path.join(MVSdir,'Vc7')
275
276         MVSCommondir = r'%s\Common7' % MVSdir
277         if use_mfc_dirs:
278             mfc_include_ = r'%s\atlmfc\include;' % MVSVCdir
279             mfc_lib_ = r'%s\atlmfc\lib;' % MVSVCdir
280         else:
281             mfc_include_ = ''
282             mfc_lib_ = ''
283         include_path = r'%s%s\include;%s\PlatformSDK\include' % (mfc_include_, MVSVCdir, MVSVCdir)
284         lib_path = r'%s%s\lib;%s\PlatformSDK\lib' % (mfc_lib_, MVSVCdir, MVSVCdir)
285         exe_path = r'%s\IDE;%s\bin;%s\Tools;%s\Tools\bin' % (MVSCommondir,MVSVCdir, MVSCommondir, MVSCommondir )
286
287         if SCons.Util.can_read_reg and paths.has_key('FRAMEWORKSDKDIR'):
288             include_path = include_path + r';%s\include'%paths['FRAMEWORKSDKDIR']
289             lib_path = lib_path + r';%s\lib'%paths['FRAMEWORKSDKDIR']
290             exe_path = exe_path + r';%s\bin'%paths['FRAMEWORKSDKDIR']
291
292         if SCons.Util.can_read_reg and paths.has_key('FRAMEWORKDIR') and paths.has_key('FRAMEWORKVERSION'):
293             exe_path = exe_path + r';%s\%s'%(paths['FRAMEWORKDIR'],paths['FRAMEWORKVERSION'])
294
295     return (include_path, lib_path, exe_path)
296
297 def get_msvc_paths(version=None, use_mfc_dirs=0):
298     """Return a 3-tuple of (INCLUDE, LIB, PATH) as the values
299     of those three environment variables that should be set
300     in order to execute the MSVC tools properly."""
301     exe_path = ''
302     lib_path = ''
303     include_path = ''
304
305     if not version:
306         versions = SCons.Tool.msvs.get_visualstudio_versions()
307         if versions:
308             version = versions[0] #use highest version by default
309         else:
310             version = '6.0'
311
312     # Some of the configured directories only
313     # appear if the user changes them from the default.
314     # Therefore, we'll see if we can get the path to the MSDev
315     # base installation from the registry and deduce the default
316     # directories.
317     if float(version) >= 7.0:
318         defpaths = _get_msvc7_default_paths(version, use_mfc_dirs)
319     else:
320         defpaths = _get_msvc6_default_paths(version, use_mfc_dirs)
321
322     try:
323         include_path = get_msvc_path("include", version)
324     except (SCons.Util.RegError, SCons.Errors.InternalError):
325         include_path = defpaths[0]
326
327     try:
328         lib_path = get_msvc_path("lib", version)
329     except (SCons.Util.RegError, SCons.Errors.InternalError):
330         lib_path = defpaths[1]
331
332     try:
333         exe_path = get_msvc_path("path", version)
334     except (SCons.Util.RegError, SCons.Errors.InternalError):
335         exe_path = defpaths[2]
336
337     return (include_path, lib_path, exe_path)
338
339 def get_msvc_default_paths(version=None, use_mfc_dirs=0):
340     """Return a 3-tuple of (INCLUDE, LIB, PATH) as the values of those
341     three environment variables that should be set in order to execute
342     the MSVC tools properly.  This will only return the default
343     locations for the tools, not the values used by MSVS in their
344     directory setup area.  This can help avoid problems with different
345     developers having different settings, and should allow the tools
346     to run in most cases."""
347
348     if not version and not SCons.Util.can_read_reg:
349         version = '6.0'
350
351     try:
352         if not version:
353             version = SCons.Tool.msvs.get_visualstudio_versions()[0] #use highest version
354     except KeyboardInterrupt:
355         raise
356     except:
357         pass
358
359     if float(version) >= 7.0:
360         return _get_msvc7_default_paths(version, use_mfc_dirs)
361     else:
362         return _get_msvc6_default_paths(version, use_mfc_dirs)
363
364 def validate_vars(env):
365     """Validate the PCH and PCHSTOP construction variables."""
366     if env.has_key('PCH') and env['PCH']:
367         if not env.has_key('PCHSTOP'):
368             raise SCons.Errors.UserError, "The PCHSTOP construction must be defined if PCH is defined."
369         if not SCons.Util.is_String(env['PCHSTOP']):
370             raise SCons.Errors.UserError, "The PCHSTOP construction variable must be a string: %r"%env['PCHSTOP']
371
372 def pch_emitter(target, source, env):
373     """Adds the object file target."""
374
375     validate_vars(env)
376
377     pch = None
378     obj = None
379
380     for t in target:
381         if SCons.Util.splitext(str(t))[1] == '.pch':
382             pch = t
383         if SCons.Util.splitext(str(t))[1] == '.obj':
384             obj = t
385
386     if not obj:
387         obj = SCons.Util.splitext(str(pch))[0]+'.obj'
388
389     target = [pch, obj] # pch must be first, and obj second for the PCHCOM to work
390
391     return (target, source)
392
393 def object_emitter(target, source, env, parent_emitter):
394     """Sets up the PCH dependencies for an object file."""
395
396     validate_vars(env)
397
398     parent_emitter(target, source, env)
399
400     if env.has_key('PCH') and env['PCH']:
401         env.Depends(target, env['PCH'])
402
403     return (target, source)
404
405 def static_object_emitter(target, source, env):
406     return object_emitter(target, source, env,
407                           SCons.Defaults.StaticObjectEmitter)
408
409 def shared_object_emitter(target, source, env):
410     return object_emitter(target, source, env,
411                           SCons.Defaults.SharedObjectEmitter)
412
413 pch_action = SCons.Action.Action('$PCHCOM', '$PCHCOMSTR')
414 pch_builder = SCons.Builder.Builder(action=pch_action, suffix='.pch',
415                                     emitter=pch_emitter,
416                                     source_scanner=SCons.Tool.SourceFileScanner)
417 res_action = SCons.Action.Action('$RCCOM', '$RCCOMSTR')
418 res_builder = SCons.Builder.Builder(action=res_action, suffix='.res',
419                                     source_scanner=SCons.Tool.SourceFileScanner)
420 SCons.Tool.SourceFileScanner.add_scanner('.rc', SCons.Defaults.CScan)
421
422 def generate(env):
423     """Add Builders and construction variables for MSVC++ to an Environment."""
424     static_obj, shared_obj = SCons.Tool.createObjBuilders(env)
425
426     for suffix in CSuffixes:
427         static_obj.add_action(suffix, SCons.Defaults.CAction)
428         shared_obj.add_action(suffix, SCons.Defaults.ShCAction)
429         static_obj.add_emitter(suffix, static_object_emitter)
430         shared_obj.add_emitter(suffix, shared_object_emitter)
431
432     for suffix in CXXSuffixes:
433         static_obj.add_action(suffix, SCons.Defaults.CXXAction)
434         shared_obj.add_action(suffix, SCons.Defaults.ShCXXAction)
435         static_obj.add_emitter(suffix, static_object_emitter)
436         shared_obj.add_emitter(suffix, shared_object_emitter)
437
438     env['CCPDBFLAGS'] = SCons.Util.CLVar(['${(PDB and "/Z7") or ""}'])
439     env['CCPCHFLAGS'] = SCons.Util.CLVar(['${(PCH and "/Yu%s /Fp%s"%(PCHSTOP or "",File(PCH))) or ""}'])
440     env['CCCOMFLAGS'] = '$CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS /c $SOURCES /Fo$TARGET $CCPCHFLAGS $CCPDBFLAGS'
441     env['CC']         = 'cl'
442     env['CCFLAGS']    = SCons.Util.CLVar('/nologo')
443     env['CCCOM']      = '$CC $CCFLAGS $CCCOMFLAGS'
444     env['SHCC']       = '$CC'
445     env['SHCCFLAGS']  = SCons.Util.CLVar('$CCFLAGS')
446     env['SHCCCOM']    = '$SHCC $SHCCFLAGS $CCCOMFLAGS'
447     env['CXX']        = '$CC'
448     env['CXXFLAGS']   = SCons.Util.CLVar('$CCFLAGS $( /TP $)')
449     env['CXXCOM']     = '$CXX $CXXFLAGS $CCCOMFLAGS'
450     env['SHCXX']      = '$CXX'
451     env['SHCXXFLAGS'] = SCons.Util.CLVar('$CXXFLAGS')
452     env['SHCXXCOM']   = '$SHCXX $SHCXXFLAGS $CCCOMFLAGS'
453     env['CPPDEFPREFIX']  = '/D'
454     env['CPPDEFSUFFIX']  = ''
455     env['INCPREFIX']  = '/I'
456     env['INCSUFFIX']  = ''
457 #    env.Append(OBJEMITTER = [static_object_emitter])
458 #    env.Append(SHOBJEMITTER = [shared_object_emitter])
459     env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 1
460
461     env['RC'] = 'rc'
462     env['RCFLAGS'] = SCons.Util.CLVar('')
463     env['RCCOM'] = '$RC $_CPPDEFFLAGS $_CPPINCFLAGS $RCFLAGS /fo$TARGET $SOURCES'
464     env['BUILDERS']['RES'] = res_builder
465     env['OBJPREFIX']      = ''
466     env['OBJSUFFIX']      = '.obj'
467     env['SHOBJPREFIX']    = '$OBJPREFIX'
468     env['SHOBJSUFFIX']    = '$OBJSUFFIX'
469
470     try:
471         version = SCons.Tool.msvs.get_default_visualstudio_version(env)
472
473         use_mfc_dirs = env.get('MSVS_USE_MFC_DIRS', 0)
474         if env.get('MSVS_IGNORE_IDE_PATHS', 0):
475             _get_paths = get_msvc_default_paths
476         else:
477             _get_paths = get_msvc_paths
478         include_path, lib_path, exe_path = _get_paths(version, use_mfc_dirs)
479
480         # since other tools can set these, we just make sure that the
481         # relevant stuff from MSVS is in there somewhere.
482         env.PrependENVPath('INCLUDE', include_path)
483         env.PrependENVPath('LIB', lib_path)
484         env.PrependENVPath('PATH', exe_path)
485     except (SCons.Util.RegError, SCons.Errors.InternalError):
486         pass
487
488     env['CFILESUFFIX'] = '.c'
489     env['CXXFILESUFFIX'] = '.cc'
490
491     env['PCHPDBFLAGS'] = SCons.Util.CLVar(['${(PDB and "/Yd") or ""}'])
492     env['PCHCOM'] = '$CXX $CXXFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS /c $SOURCES /Fo${TARGETS[1]} /Yc$PCHSTOP /Fp${TARGETS[0]} $CCPDBFLAGS $PCHPDBFLAGS'
493     env['BUILDERS']['PCH'] = pch_builder
494
495 def exists(env):
496     if SCons.Tool.msvs.is_msvs_installed():
497         # there's at least one version of MSVS installed.
498         return 1
499     else:
500         return env.Detect('cl')
501