Initialize *FLAGS variables with objects that can add flags either as strings or...
[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):
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         include_path = r'%s\ATL\include;%s\MFC\include;%s\include' % (MVSVCdir, MVSVCdir, MVSVCdir)
221         lib_path = r'%s\MFC\lib;%s\lib' % (MVSVCdir, MVSVCdir)
222         exe_path = r'%s\MSDev98\bin;%s\bin' % (MVSCommondir, MVSVCdir)
223     return (include_path, lib_path, exe_path)
224
225 def _get_msvc7_default_paths(version):
226     """Return a 3-tuple of (INCLUDE, LIB, PATH) as the values of those
227     three environment variables that should be set in order to execute
228     the MSVC .NET tools properly, if the information wasn't available
229     from the registry."""
230             
231     MVSdir = None
232     paths = {}
233     exe_path = ''
234     lib_path = ''
235     include_path = ''
236     try:
237         paths = SCons.Tool.msvs.get_msvs_install_dirs(version)
238         MVSdir = paths['VSINSTALLDIR']
239     except (KeyError, SCons.Util.RegError, SCons.Errors.InternalError):
240         if os.environ.has_key('VSCOMNTOOLS'):
241             MVSdir = os.path.normpath(os.path.join(os.environ['VSCOMNTOOLS'],'..','..'))
242         else:
243             # last resort -- default install location
244             MVSdir = r'C:\Program Files\Microsoft Visual Studio .NET'
245
246     if not MVSdir:
247         if SCons.Util.can_read_reg and paths.has_key('VCINSTALLDIR'):
248             MVSVCdir = paths['VCINSTALLDIR']
249         else:
250             MVSVCdir = os.path.join(MVSdir,'Vc7')
251
252         MVSCommondir = r'%s\Common7' % MVSdir
253         include_path = r'%s\atlmfc\include;%s\include' % (MVSVCdir, MVSVCdir, MVSVCdir)
254         lib_path = r'%s\atlmfc\lib;%s\lib' % (MVSVCdir, MVSVCdir)
255         exe_path = r'%s\Tools\bin;%s\Tools;%s\bin' % (MVSCommondir, MVSCommondir, MVSVCdir)
256     return (include_path, lib_path, exe_path)
257
258 def get_msvc_paths(version=None):
259     """Return a 3-tuple of (INCLUDE, LIB, PATH) as the values
260     of those three environment variables that should be set
261     in order to execute the MSVC tools properly."""
262     exe_path = ''
263     lib_path = ''
264     include_path = ''
265
266     if not version:
267         versions = SCons.Tool.msvs.get_visualstudio_versions()
268         if versions:
269             version = versions[0] #use highest version by default
270         else:
271             version = '6.0'
272
273     # Some of the configured directories only
274     # appear if the user changes them from the default.
275     # Therefore, we'll see if we can get the path to the MSDev
276     # base installation from the registry and deduce the default
277     # directories.
278     if float(version) >= 7.0:
279         defpaths = _get_msvc7_default_paths(version)
280     else:
281         defpaths = _get_msvc6_default_paths(version)
282     
283     try:
284         include_path = get_msvc_path("include", version)
285     except (SCons.Util.RegError, SCons.Errors.InternalError):
286         include_path = defpaths[0]
287
288     try:
289         lib_path = get_msvc_path("lib", version)
290     except (SCons.Util.RegError, SCons.Errors.InternalError):
291         lib_path = defpaths[1]
292
293     try:
294         exe_path = get_msvc_path("path", version)
295     except (SCons.Util.RegError, SCons.Errors.InternalError):
296         exe_path = defpaths[2]
297     
298     return (include_path, lib_path, exe_path)
299
300 def get_msvc_default_paths(version = None):
301     """Return a 3-tuple of (INCLUDE, LIB, PATH) as the values of those
302     three environment variables that should be set in order to execute
303     the MSVC tools properly.  This will only return the default
304     locations for the tools, not the values used by MSVS in their
305     directory setup area.  This can help avoid problems with different
306     developers having different settings, and should allow the tools
307     to run in most cases."""
308
309     if not version and not SCons.Util.can_read_reg:
310         version = '6.0'
311
312     try:
313         if not version:
314             version = SCons.Tool.msvs.get_visualstudio_versions()[0] #use highest version
315     except KeyboardInterrupt:
316         raise
317     except:
318         pass
319
320     if float(version) >= 7.0:
321         return _get_msvc7_default_paths(version)
322     else:
323         return _get_msvc6_default_paths(version)
324
325 def validate_vars(env):
326     """Validate the PDB, PCH, and PCHSTOP construction variables."""
327     if env.has_key('PCH') and env['PCH']:
328         if not env.has_key('PCHSTOP'):
329             raise SCons.Errors.UserError, "The PCHSTOP construction must be defined if PCH is defined."
330         if not SCons.Util.is_String(env['PCHSTOP']):
331             raise SCons.Errors.UserError, "The PCHSTOP construction variable must be a string: %r"%env['PCHSTOP']
332
333 def pch_emitter(target, source, env):
334     """Sets up the PDB dependencies for a pch file, and adds the object
335     file target."""
336
337     validate_vars(env)
338
339     pch = None
340     obj = None
341
342     for t in target:
343         if SCons.Util.splitext(str(t))[1] == '.pch':
344             pch = t
345         if SCons.Util.splitext(str(t))[1] == '.obj':
346             obj = t
347
348     if not obj:
349         obj = SCons.Util.splitext(str(pch))[0]+'.obj'
350
351     target = [pch, obj] # pch must be first, and obj second for the PCHCOM to work
352
353     if env.has_key('PDB') and env['PDB']:
354         env.SideEffect(env['PDB'], target)
355         env.Precious(env['PDB'])
356
357     return (target, source)
358
359 def object_emitter(target, source, env, parent_emitter):
360     """Sets up the PDB and PCH dependencies for an object file."""
361
362     validate_vars(env)
363
364     parent_emitter(target, source, env)
365
366     if env.has_key('PDB') and env['PDB']:
367         env.SideEffect(env['PDB'], target)
368         env.Precious(env['PDB'])
369
370     if env.has_key('PCH') and env['PCH']:
371         env.Depends(target, env['PCH'])
372
373     return (target, source)
374
375 def static_object_emitter(target, source, env):
376     return object_emitter(target, source, env,
377                           SCons.Defaults.StaticObjectEmitter)
378
379 def shared_object_emitter(target, source, env):
380     return object_emitter(target, source, env,
381                           SCons.Defaults.SharedObjectEmitter)
382
383 pch_builder = SCons.Builder.Builder(action='$PCHCOM', suffix='.pch', emitter=pch_emitter)
384 res_builder = SCons.Builder.Builder(action='$RCCOM', suffix='.res')
385
386 def generate(env):
387     """Add Builders and construction variables for MSVC++ to an Environment."""
388     static_obj, shared_obj = SCons.Tool.createObjBuilders(env)
389
390     for suffix in CSuffixes:
391         static_obj.add_action(suffix, SCons.Defaults.CAction)
392         shared_obj.add_action(suffix, SCons.Defaults.ShCAction)
393
394     for suffix in CXXSuffixes:
395         static_obj.add_action(suffix, SCons.Defaults.CXXAction)
396         shared_obj.add_action(suffix, SCons.Defaults.ShCXXAction)
397
398     env['CCPDBFLAGS'] = SCons.Util.CLVar(['${(PDB and "/Zi /Fd%s"%File(PDB)) or ""}'])
399     env['CCPCHFLAGS'] = SCons.Util.CLVar(['${(PCH and "/Yu%s /Fp%s"%(PCHSTOP or "",File(PCH))) or ""}'])
400     env['CCCOMFLAGS'] = '$CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS /c $SOURCES /Fo$TARGET $CCPCHFLAGS $CCPDBFLAGS'
401     env['CC']         = 'cl'
402     env['CCFLAGS']    = SCons.Util.CLVar('/nologo')
403     env['CCCOM']      = '$CC $CCFLAGS $CCCOMFLAGS'
404     env['SHCC']       = '$CC'
405     env['SHCCFLAGS']  = SCons.Util.CLVar('$CCFLAGS')
406     env['SHCCCOM']    = '$SHCC $SHCCFLAGS $CCCOMFLAGS'
407     env['CXX']        = '$CC'
408     env['CXXFLAGS']   = SCons.Util.CLVar('$CCFLAGS $( /TP $)')
409     env['CXXCOM']     = '$CXX $CXXFLAGS $CCCOMFLAGS'
410     env['SHCXX']      = '$CXX'
411     env['SHCXXFLAGS'] = SCons.Util.CLVar('$CXXFLAGS')
412     env['SHCXXCOM']   = '$SHCXX $SHCXXFLAGS $CCCOMFLAGS'
413     env['CPPDEFPREFIX']  = '/D'
414     env['CPPDEFSUFFIX']  = ''
415     env['INCPREFIX']  = '/I'
416     env['INCSUFFIX']  = ''
417     env['OBJEMITTER'] = static_object_emitter
418     env['SHOBJEMITTER'] = shared_object_emitter
419     env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 1
420
421     env['RC'] = 'rc'
422     env['RCFLAGS'] = SCons.Util.CLVar('')
423     env['RCCOM'] = '$RC $_CPPDEFFLAGS $_CPPINCFLAGS $RCFLAGS /fo$TARGET $SOURCES'
424     CScan = env.get_scanner('.c')
425     if CScan:
426         CScan.add_skey('.rc')
427     env['BUILDERS']['RES'] = res_builder
428
429     try:
430         version = SCons.Tool.msvs.get_default_visualstudio_version(env)
431
432         if env.has_key('MSVS_IGNORE_IDE_PATHS') and env['MSVS_IGNORE_IDE_PATHS']:
433             include_path, lib_path, exe_path = get_msvc_default_paths(version)
434         else:
435             include_path, lib_path, exe_path = get_msvc_paths(version)
436
437         # since other tools can set these, we just make sure that the
438         # relevant stuff from MSVS is in there somewhere.
439         env.PrependENVPath('INCLUDE', include_path)
440         env.PrependENVPath('LIB', lib_path)
441         env.PrependENVPath('PATH', exe_path)
442     except (SCons.Util.RegError, SCons.Errors.InternalError):
443         pass
444
445     env['CFILESUFFIX'] = '.c'
446     env['CXXFILESUFFIX'] = '.cc'
447
448     env['PCHCOM'] = '$CXX $CXXFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS /c $SOURCES /Fo${TARGETS[1]} /Yc$PCHSTOP /Fp${TARGETS[0]} $CCPDBFLAGS'
449     env['BUILDERS']['PCH'] = pch_builder
450
451 def exists(env):
452     try:
453         v = SCons.Tool.msvs.get_visualstudio_versions()
454     except (SCons.Util.RegError, SCons.Errors.InternalError):
455         pass
456     
457     if not v:
458         return env.Detect('cl')
459     else:
460         # there's at least one version of MSVS installed.
461         return 1