Restore scanning of .pch files. (Anthony Roach)
[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         f = open(comps,'r')
75         line = f.readline()
76         found = 0
77         while line:
78             line.strip()
79             if line.find(r'[VC\VC_OBJECTS_PLATFORM_INFO\Win32\Directories]') >= 0:
80                 found = 1
81             elif line == '' or line[:1] == '[':
82                 found = 0
83             elif found == 1:
84                 kv = line.split('=', 1)
85                 if len(kv) == 2:
86                     (key, val) = kv
87                 key = key.replace(' Dirs','')
88                 dirs[key.upper()] = val
89             line = f.readline()
90         f.close()
91     else:
92         # since the file didn't exist, we have only the defaults in
93         # the registry to work with.
94         try:
95             K = 'SOFTWARE\\Microsoft\\VisualStudio\\' + version
96             K = K + r'\VC\VC_OBJECTS_PLATFORM_INFO\Win32\Directories'
97             k = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE,K)
98             i = 0
99             while 1:
100                 try:
101                     (key,val,t) = SCons.Util.RegEnumValue(k,i)
102                     key = key.replace(' Dirs','')
103                     dirs[key.upper()] = val
104                     i = i + 1
105                 except SCons.Util.RegError:
106                     break
107         except SCons.Util.RegError:
108             # if we got here, then we didn't find the registry entries:
109             raise SCons.Errors.InternalError, "Unable to find MSVC paths in the registry."
110     return dirs
111
112 def _get_msvc7_path(path, version, platform):
113     """
114     Get Visual Studio directories from version 7 (MSVS .NET)
115     (it has a different registry structure than versions before it)
116     """
117     # first, look for a customization of the default values in the
118     # registry: These are sometimes stored in the Local Settings area
119     # for Visual Studio, in a file, so we have to parse it.
120     dirs = _parse_msvc7_overrides(version)
121
122     if dirs.has_key(path):
123         p = dirs[path]
124     else:
125         raise SCons.Errors.InternalError, "Unable to retrieve the %s path from MS VC++."%path
126
127     # collect some useful information for later expansions...
128     paths = SCons.Tool.msvs.get_msvs_install_dirs(version)
129
130     # expand the directory path variables that we support.  If there
131     # is a variable we don't support, then replace that entry with
132     # "---Unknown Location VSInstallDir---" or something similar, to clue
133     # people in that we didn't find something, and so env expansion doesn't
134     # do weird things with the $(xxx)'s
135     s = re.compile('\$\(([a-zA-Z0-9_]+?)\)')
136
137     def repl(match, paths=paths):
138         key = string.upper(match.group(1))
139         if paths.has_key(key):
140             return paths[key]
141         else:
142             return '---Unknown Location %s---' % match.group()
143
144     rv = []
145     for entry in p.split(os.pathsep):
146         entry = s.sub(repl,entry)
147         rv.append(entry)
148
149     return string.join(rv,os.pathsep)
150
151 def get_msvc_path (path, version, platform='x86'):
152     """
153     Get a list of visualstudio directories (include, lib or path).  Return
154     a string delimited by ';'. An exception will be raised if unable to
155     access the registry or appropriate registry keys not found.
156     """
157
158     if not SCons.Util.can_read_reg:
159         raise SCons.Errors.InternalError, "No Windows registry module was found"
160
161     # normalize the case for comparisons (since the registry is case
162     # insensitive)
163     path = string.upper(path)
164
165     if path=='LIB':
166         path= 'LIBRARY'
167
168     if float(version) >= 7.0:
169         return _get_msvc7_path(path, version, platform)
170
171     path = string.upper(path + ' Dirs')
172     K = ('Software\\Microsoft\\Devstudio\\%s\\' +
173          'Build System\\Components\\Platforms\\Win32 (%s)\\Directories') % \
174         (version,platform)
175     for base in (SCons.Util.HKEY_CURRENT_USER,
176                  SCons.Util.HKEY_LOCAL_MACHINE):
177         try:
178             k = SCons.Util.RegOpenKeyEx(base,K)
179             i = 0
180             while 1:
181                 try:
182                     (p,v,t) = SCons.Util.RegEnumValue(k,i)
183                     if string.upper(p) == path:
184                         return v
185                     i = i + 1
186                 except SCons.Util.RegError:
187                     break
188         except SCons.Util.RegError:
189             pass
190
191     # if we got here, then we didn't find the registry entries:
192     raise SCons.Errors.InternalError, "The %s path was not found in the registry."%path
193
194 def _get_msvc6_default_paths(version, use_mfc_dirs):
195     """Return a 3-tuple of (INCLUDE, LIB, PATH) as the values of those
196     three environment variables that should be set in order to execute
197     the MSVC 6.0 tools properly, if the information wasn't available
198     from the registry."""
199     MVSdir = None
200     paths = {}
201     exe_path = ''
202     lib_path = ''
203     include_path = ''
204     try:
205         paths = SCons.Tool.msvs.get_msvs_install_dirs(version)
206         MVSdir = paths['VSINSTALLDIR']
207     except (SCons.Util.RegError, SCons.Errors.InternalError, KeyError):
208         if os.environ.has_key('MSDEVDIR'):
209             MVSdir = os.path.normpath(os.path.join(os.environ['MSDEVDIR'],'..','..'))
210         else:
211             MVSdir = r'C:\Program Files\Microsoft Visual Studio'
212     if MVSdir:
213         if SCons.Util.can_read_reg and paths.has_key('VCINSTALLDIR'):
214             MVSVCdir = paths['VCINSTALLDIR']
215         else:
216             MVSVCdir = os.path.join(MVSdir,'VC98')
217
218         MVSCommondir = r'%s\Common' % MVSdir
219         if use_mfc_dirs:
220             mfc_include_ = r'%s\ATL\include;%s\MFC\include;' % (MVSVCdir, MVSVCdir)
221             mfc_lib_ = r'%s\MFC\lib;' % MVSVCdir
222         else:
223             mfc_include_ = ''
224             mfc_lib_ = ''
225         include_path = r'%s%s\include' % (mfc_include_, MVSVCdir)
226         lib_path = r'%s%s\lib' % (mfc_lib_, MVSVCdir)
227
228         if os.environ.has_key('OS') and os.environ['OS'] == "Windows_NT":
229             osdir = 'WINNT'
230         else:
231             osdir = 'WIN95'
232
233         exe_path = r'%s\tools\%s;%s\MSDev98\bin;%s\tools;%s\bin' % (MVSCommondir, osdir, MVSCommondir,  MVSCommondir, MVSVCdir)
234     return (include_path, lib_path, exe_path)
235
236 def _get_msvc7_default_paths(version, use_mfc_dirs):
237     """Return a 3-tuple of (INCLUDE, LIB, PATH) as the values of those
238     three environment variables that should be set in order to execute
239     the MSVC .NET tools properly, if the information wasn't available
240     from the registry."""
241
242     MVSdir = None
243     paths = {}
244     exe_path = ''
245     lib_path = ''
246     include_path = ''
247     try:
248         paths = SCons.Tool.msvs.get_msvs_install_dirs(version)
249         MVSdir = paths['VSINSTALLDIR']
250     except (KeyError, SCons.Util.RegError, SCons.Errors.InternalError):
251         if os.environ.has_key('VSCOMNTOOLS'):
252             MVSdir = os.path.normpath(os.path.join(os.environ['VSCOMNTOOLS'],'..','..'))
253         else:
254             # last resort -- default install location
255             MVSdir = r'C:\Program Files\Microsoft Visual Studio .NET'
256
257     if MVSdir:
258         if SCons.Util.can_read_reg and paths.has_key('VCINSTALLDIR'):
259             MVSVCdir = paths['VCINSTALLDIR']
260         else:
261             MVSVCdir = os.path.join(MVSdir,'Vc7')
262
263         MVSCommondir = r'%s\Common7' % MVSdir
264         if use_mfc_dirs:
265             mfc_include_ = r'%s\atlmfc\include;' % MVSVCdir
266             mfc_lib_ = r'%s\atlmfc\lib;' % MVSVCdir
267         else:
268             mfc_include_ = ''
269             mfc_lib_ = ''
270         include_path = r'%s%s\include;%s\PlatformSDK\include' % (mfc_include_, MVSVCdir, MVSVCdir)
271         lib_path = r'%s%s\lib;%s\PlatformSDK\lib' % (mfc_lib_, MVSVCdir, MVSVCdir)
272         exe_path = r'%s\IDE;%s\bin;%s\Tools;%s\Tools\bin' % (MVSCommondir,MVSVCdir, MVSCommondir, MVSCommondir )
273
274         if SCons.Util.can_read_reg and paths.has_key('FRAMEWORKSDKDIR'):
275             include_path = include_path + r';%s\include'%paths['FRAMEWORKSDKDIR']
276             lib_path = lib_path + r';%s\lib'%paths['FRAMEWORKSDKDIR']
277             exe_path = exe_path + r';%s\bin'%paths['FRAMEWORKSDKDIR']
278
279         if SCons.Util.can_read_reg and paths.has_key('FRAMEWORKDIR') and paths.has_key('FRAMEWORKVERSION'):
280             exe_path = exe_path + r';%s\%s'%(paths['FRAMEWORKDIR'],paths['FRAMEWORKVERSION'])
281
282     return (include_path, lib_path, exe_path)
283
284 def get_msvc_paths(version=None, use_mfc_dirs=0):
285     """Return a 3-tuple of (INCLUDE, LIB, PATH) as the values
286     of those three environment variables that should be set
287     in order to execute the MSVC tools properly."""
288     exe_path = ''
289     lib_path = ''
290     include_path = ''
291
292     if not version:
293         versions = SCons.Tool.msvs.get_visualstudio_versions()
294         if versions:
295             version = versions[0] #use highest version by default
296         else:
297             version = '6.0'
298
299     # Some of the configured directories only
300     # appear if the user changes them from the default.
301     # Therefore, we'll see if we can get the path to the MSDev
302     # base installation from the registry and deduce the default
303     # directories.
304     if float(version) >= 7.0:
305         defpaths = _get_msvc7_default_paths(version, use_mfc_dirs)
306     else:
307         defpaths = _get_msvc6_default_paths(version, use_mfc_dirs)
308
309     try:
310         include_path = get_msvc_path("include", version)
311     except (SCons.Util.RegError, SCons.Errors.InternalError):
312         include_path = defpaths[0]
313
314     try:
315         lib_path = get_msvc_path("lib", version)
316     except (SCons.Util.RegError, SCons.Errors.InternalError):
317         lib_path = defpaths[1]
318
319     try:
320         exe_path = get_msvc_path("path", version)
321     except (SCons.Util.RegError, SCons.Errors.InternalError):
322         exe_path = defpaths[2]
323
324     return (include_path, lib_path, exe_path)
325
326 def get_msvc_default_paths(version=None, use_mfc_dirs=0):
327     """Return a 3-tuple of (INCLUDE, LIB, PATH) as the values of those
328     three environment variables that should be set in order to execute
329     the MSVC tools properly.  This will only return the default
330     locations for the tools, not the values used by MSVS in their
331     directory setup area.  This can help avoid problems with different
332     developers having different settings, and should allow the tools
333     to run in most cases."""
334
335     if not version and not SCons.Util.can_read_reg:
336         version = '6.0'
337
338     try:
339         if not version:
340             version = SCons.Tool.msvs.get_visualstudio_versions()[0] #use highest version
341     except KeyboardInterrupt:
342         raise
343     except:
344         pass
345
346     if float(version) >= 7.0:
347         return _get_msvc7_default_paths(version, use_mfc_dirs)
348     else:
349         return _get_msvc6_default_paths(version, use_mfc_dirs)
350
351 def validate_vars(env):
352     """Validate the PCH and PCHSTOP construction variables."""
353     if env.has_key('PCH') and env['PCH']:
354         if not env.has_key('PCHSTOP'):
355             raise SCons.Errors.UserError, "The PCHSTOP construction must be defined if PCH is defined."
356         if not SCons.Util.is_String(env['PCHSTOP']):
357             raise SCons.Errors.UserError, "The PCHSTOP construction variable must be a string: %r"%env['PCHSTOP']
358
359 def pch_emitter(target, source, env):
360     """Adds the object file target."""
361
362     validate_vars(env)
363
364     pch = None
365     obj = None
366
367     for t in target:
368         if SCons.Util.splitext(str(t))[1] == '.pch':
369             pch = t
370         if SCons.Util.splitext(str(t))[1] == '.obj':
371             obj = t
372
373     if not obj:
374         obj = SCons.Util.splitext(str(pch))[0]+'.obj'
375
376     target = [pch, obj] # pch must be first, and obj second for the PCHCOM to work
377
378     return (target, source)
379
380 def object_emitter(target, source, env, parent_emitter):
381     """Sets up the PCH dependencies for an object file."""
382
383     validate_vars(env)
384
385     parent_emitter(target, source, env)
386
387     if env.has_key('PCH') and env['PCH']:
388         env.Depends(target, env['PCH'])
389
390     return (target, source)
391
392 def static_object_emitter(target, source, env):
393     return object_emitter(target, source, env,
394                           SCons.Defaults.StaticObjectEmitter)
395
396 def shared_object_emitter(target, source, env):
397     return object_emitter(target, source, env,
398                           SCons.Defaults.SharedObjectEmitter)
399
400 pch_builder = SCons.Builder.Builder(action='$PCHCOM', suffix='.pch', emitter=pch_emitter,
401                                     source_scanner=SCons.Defaults.ObjSourceScan)
402 res_builder = SCons.Builder.Builder(action='$RCCOM', suffix='.res',
403                                     source_scanner=SCons.Defaults.ObjSourceScan)
404 SCons.Defaults.ObjSourceScan.add_scanner('.rc', SCons.Defaults.CScan)
405
406 def generate(env):
407     """Add Builders and construction variables for MSVC++ to an Environment."""
408     static_obj, shared_obj = SCons.Tool.createObjBuilders(env)
409
410     for suffix in CSuffixes:
411         static_obj.add_action(suffix, SCons.Defaults.CAction)
412         shared_obj.add_action(suffix, SCons.Defaults.ShCAction)
413         static_obj.add_emitter(suffix, static_object_emitter)
414         shared_obj.add_emitter(suffix, shared_object_emitter)
415
416     for suffix in CXXSuffixes:
417         static_obj.add_action(suffix, SCons.Defaults.CXXAction)
418         shared_obj.add_action(suffix, SCons.Defaults.ShCXXAction)
419         static_obj.add_emitter(suffix, static_object_emitter)
420         shared_obj.add_emitter(suffix, shared_object_emitter)
421
422     env['CCPDBFLAGS'] = SCons.Util.CLVar(['${(PDB and "/Z7") or ""}'])
423     env['CCPCHFLAGS'] = SCons.Util.CLVar(['${(PCH and "/Yu%s /Fp%s"%(PCHSTOP or "",File(PCH))) or ""}'])
424     env['CCCOMFLAGS'] = '$CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS /c $SOURCES /Fo$TARGET $CCPCHFLAGS $CCPDBFLAGS'
425     env['CC']         = 'cl'
426     env['CCFLAGS']    = SCons.Util.CLVar('/nologo')
427     env['CCCOM']      = '$CC $CCFLAGS $CCCOMFLAGS'
428     env['SHCC']       = '$CC'
429     env['SHCCFLAGS']  = SCons.Util.CLVar('$CCFLAGS')
430     env['SHCCCOM']    = '$SHCC $SHCCFLAGS $CCCOMFLAGS'
431     env['CXX']        = '$CC'
432     env['CXXFLAGS']   = SCons.Util.CLVar('$CCFLAGS $( /TP $)')
433     env['CXXCOM']     = '$CXX $CXXFLAGS $CCCOMFLAGS'
434     env['SHCXX']      = '$CXX'
435     env['SHCXXFLAGS'] = SCons.Util.CLVar('$CXXFLAGS')
436     env['SHCXXCOM']   = '$SHCXX $SHCXXFLAGS $CCCOMFLAGS'
437     env['CPPDEFPREFIX']  = '/D'
438     env['CPPDEFSUFFIX']  = ''
439     env['INCPREFIX']  = '/I'
440     env['INCSUFFIX']  = ''
441 #    env.Append(OBJEMITTER = [static_object_emitter])
442 #    env.Append(SHOBJEMITTER = [shared_object_emitter])
443     env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 1
444
445     env['RC'] = 'rc'
446     env['RCFLAGS'] = SCons.Util.CLVar('')
447     env['RCCOM'] = '$RC $_CPPDEFFLAGS $_CPPINCFLAGS $RCFLAGS /fo$TARGET $SOURCES'
448     env['BUILDERS']['RES'] = res_builder
449     env['OBJPREFIX']      = ''
450     env['OBJSUFFIX']      = '.obj'
451     env['SHOBJPREFIX']    = '$OBJPREFIX'
452     env['SHOBJSUFFIX']    = '$OBJSUFFIX'
453
454     try:
455         version = SCons.Tool.msvs.get_default_visualstudio_version(env)
456
457         # By default, add the MFC directories, because this is what
458         # we've been doing for a long time.  We may change this.
459         use_mfc_dirs = env.get('MSVS_USE_MFC_DIRS', 1)
460         if env.get('MSVS_IGNORE_IDE_PATHS', 0):
461             _get_paths = get_msvc_default_paths
462         else:
463             _get_paths = get_msvc_paths
464         include_path, lib_path, exe_path = _get_paths(version, use_mfc_dirs)
465
466         # since other tools can set these, we just make sure that the
467         # relevant stuff from MSVS is in there somewhere.
468         env.PrependENVPath('INCLUDE', include_path)
469         env.PrependENVPath('LIB', lib_path)
470         env.PrependENVPath('PATH', exe_path)
471     except (SCons.Util.RegError, SCons.Errors.InternalError):
472         pass
473
474     env['CFILESUFFIX'] = '.c'
475     env['CXXFILESUFFIX'] = '.cc'
476
477     env['PCHPDBFLAGS'] = SCons.Util.CLVar(['${(PDB and "/Yd") or ""}'])
478     env['PCHCOM'] = '$CXX $CXXFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS /c $SOURCES /Fo${TARGETS[1]} /Yc$PCHSTOP /Fp${TARGETS[0]} $CCPDBFLAGS $PCHPDBFLAGS'
479     env['BUILDERS']['PCH'] = pch_builder
480
481 def exists(env):
482     if SCons.Tool.msvs.is_msvs_installed():
483         # there's at least one version of MSVS installed.
484         return 1
485     else:
486         return env.Detect('cl')
487