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