dbb7a85ca7a02685f57a740a8d160d18de7cb164
[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 string
38
39 import SCons.Action
40 import SCons.Tool
41 import SCons.Errors
42 import SCons.Builder
43 import SCons.Util
44
45 CSuffixes = ['.c', '.C']
46 CXXSuffixes = ['.cc', '.cpp', '.cxx', '.c++', '.C++']
47
48 def get_devstudio_versions():
49     """
50     Get list of devstudio versions from the Windows registry.  Return a
51     list of strings containing version numbers; an exception will be raised
52     if we were unable to access the registry (eg. couldn't import
53     a registry-access module) or the appropriate registry keys weren't
54     found.
55     """
56
57     if not SCons.Util.can_read_reg:
58         raise SCons.Errors.InternalError, "No Windows registry module was found"
59
60     K = 'Software\\Microsoft\\Devstudio'
61     L = []
62     for base in (SCons.Util.HKEY_CLASSES_ROOT,
63                  SCons.Util.HKEY_LOCAL_MACHINE,
64                  SCons.Util.HKEY_CURRENT_USER,
65                  SCons.Util.HKEY_USERS):
66         try:
67             k = SCons.Util.RegOpenKeyEx(base,K)
68             i = 0
69             while 1:
70                 try:
71                     p = SCons.Util.RegEnumKey(k,i)
72                     if p[0] in '123456789' and p not in L:
73                         L.append(p)
74                 except SCons.Util.RegError:
75                     break
76                 i = i + 1
77         except SCons.Util.RegError:
78             pass
79
80     if not L:
81         raise SCons.Errors.InternalError, "DevStudio was not found."
82
83     L.sort()
84     L.reverse()
85     return L
86
87 def get_msvc_path (path, version, platform='x86'):
88     """
89     Get a list of devstudio directories (include, lib or path).  Return
90     a string delimited by ';'. An exception will be raised if unable to
91     access the registry or appropriate registry keys not found.
92     """
93
94     if not SCons.Util.can_read_reg:
95         raise SCons.Errors.InternalError, "No Windows registry module was found"
96
97     if path=='lib':
98         path= 'Library'
99     path = string.upper(path + ' Dirs')
100     K = ('Software\\Microsoft\\Devstudio\\%s\\' +
101          'Build System\\Components\\Platforms\\Win32 (%s)\\Directories') % \
102         (version,platform)
103     for base in (SCons.Util.HKEY_CLASSES_ROOT,
104                  SCons.Util.HKEY_LOCAL_MACHINE,
105                  SCons.Util.HKEY_CURRENT_USER,
106                  SCons.Util.HKEY_USERS):
107         try:
108             k = SCons.Util.RegOpenKeyEx(base,K)
109             i = 0
110             while 1:
111                 try:
112                     (p,v,t) = SCons.Util.RegEnumValue(k,i)
113                     if string.upper(p) == path:
114                         return v
115                     i = i + 1
116                 except SCons.Util.RegError:
117                     break
118         except SCons.Util.RegError:
119             pass
120
121     # if we got here, then we didn't find the registry entries:
122     raise SCons.Errors.InternalError, "%s was not found in the registry."%path
123
124 def get_msdev_dir(version):
125     """Returns the root directory of the MSDev installation from the
126     registry if it can be found, otherwise we guess."""
127     if SCons.Util.can_read_reg:
128         K = ('Software\\Microsoft\\Devstudio\\%s\\' +
129              'Products\\Microsoft Visual C++') % \
130              version
131         for base in (SCons.Util.HKEY_LOCAL_MACHINE,
132                      SCons.Util.HKEY_CURRENT_USER):
133             try:
134                 k = SCons.Util.RegOpenKeyEx(base,K)
135                 val, tok = SCons.Util.RegQueryValueEx(k, 'ProductDir')
136                 return os.path.split(val)[0]
137             except SCons.Util.RegError:
138                 pass
139
140 def get_msdev_paths(version=None):
141     """Return a 3-tuple of (INCLUDE, LIB, PATH) as the values
142     of those three environment variables that should be set
143     in order to execute the MSVC tools properly."""
144     exe_path = ''
145     lib_path = ''
146     include_path = ''
147     try:
148         if not version:
149             version = get_devstudio_versions()[0] #use highest version
150         include_path = get_msvc_path("include", version)
151         lib_path = get_msvc_path("lib", version)
152         exe_path = get_msvc_path("path", version) + ";" + os.environ['PATH']
153     except (SCons.Util.RegError, SCons.Errors.InternalError):
154         # Could not get the configured directories from the registry.
155         # However, the configured directories only appear if the user
156         # changes them from the default.  Therefore, we'll see if
157         # we can get the path to the MSDev base installation from
158         # the registry and deduce the default directories.
159         MVSdir = None
160         if version:
161             MVSdir = get_msdev_dir(version)
162         if MVSdir:
163             MVSVCdir = r'%s\VC98' % MVSdir
164             MVSCommondir = r'%s\Common' % MVSdir
165             include_path = r'%s\atl\include;%s\mfc\include;%s\include' % (MVSVCdir, MVSVCdir, MVSVCdir)
166             lib_path = r'%s\mfc\lib;%s\lib' % (MVSVCdir, MVSVCdir)
167             try:
168                 extra_path = os.pathsep + os.environ['PATH']
169             except KeyError:
170                 extra_path = ''
171             exe_path = (r'%s\MSDev98\Bin;%s\Bin' % (MVSCommondir, MVSVCdir)) + extra_path
172         else:
173             # The DevStudio environment variables don't exist,
174             # so just use the variables from the source environment.
175             MVSdir = r'C:\Program Files\Microsoft Visual Studio'
176             MVSVCdir = r'%s\VC98' % MVSdir
177             MVSCommondir = r'%s\Common' % MVSdir
178             try:
179                 include_path = os.environ['INCLUDE']
180             except KeyError:
181                 include_path = ''
182             try:
183                 lib_path = os.environ['LIB']
184             except KeyError:
185                 lib_path = ''
186             try:
187                 exe_path = os.environ['PATH']
188             except KeyError:
189                 exe_path = ''
190     return (include_path, lib_path, exe_path)
191
192 def validate_vars(env):
193     """Validate the PDB, PCH, and PCHSTOP construction variables."""
194     if env.has_key('PCH') and env['PCH']:
195         if not env.has_key('PCHSTOP'):
196             raise SCons.Errors.UserError, "The PCHSTOP construction must be defined if PCH is defined."
197         if not SCons.Util.is_String(env['PCHSTOP']):
198             raise SCons.Errors.UserError, "The PCHSTOP construction variable must be a string: %r"%env['PCHSTOP']
199
200 def pch_emitter(target, source, env):
201     """Sets up the PDB dependencies for a pch file, and adds the object
202     file target."""
203
204     validate_vars(env)
205
206     pch = None
207     obj = None
208
209     for t in target:
210         if os.path.splitext(str(t))[1] == '.pch':
211             pch = t
212         if os.path.splitext(str(t))[1] == '.obj':
213             obj = t
214
215     if not obj:
216         obj = os.path.splitext(str(pch))[0]+'.obj'
217
218     target = [pch, obj] # pch must be first, and obj second for the PCHCOM to work
219
220     if env.has_key('PDB') and env['PDB']:
221         env.SideEffect(env['PDB'], target)
222         env.Precious(env['PDB'])
223
224     return (target, source)
225
226 def object_emitter(target, source, env):
227     """Sets up the PDB and PCH dependencies for an object file."""
228
229     validate_vars(env)
230
231     if env.has_key('PDB') and env['PDB']:
232         env.SideEffect(env['PDB'], target)
233         env.Precious(env['PDB'])
234
235     if env.has_key('PCH') and env['PCH']:
236         env.Depends(target, env['PCH'])
237
238     return (target, source)
239
240 pch_builder = SCons.Builder.Builder(action='$PCHCOM', suffix='.pch', emitter=pch_emitter)
241 res_builder = SCons.Builder.Builder(action='$RCCOM', suffix='.res')
242
243 def generate(env, platform):
244     """Add Builders and construction variables for MSVC++ to an Environment."""
245     static_obj, shared_obj = SCons.Tool.createObjBuilders(env)
246
247     for suffix in CSuffixes:
248         static_obj.add_action(suffix, SCons.Defaults.CAction)
249         shared_obj.add_action(suffix, SCons.Defaults.ShCAction)
250
251     for suffix in CXXSuffixes:
252         static_obj.add_action(suffix, SCons.Defaults.CXXAction)
253         shared_obj.add_action(suffix, SCons.Defaults.ShCXXAction)
254
255     env['CCPDBFLAGS'] = '${(PDB and "/Zi /Fd%s"%File(PDB)) or ""}'
256     env['CCPCHFLAGS'] = '${(PCH and "/Yu%s /Fp%s"%(PCHSTOP or "",File(PCH))) or ""}'
257     env['CCCOMFLAGS'] = '$CPPFLAGS $_CPPINCFLAGS /c $SOURCES /Fo$TARGET $CCPCHFLAGS $CCPDBFLAGS'
258     env['CC']         = 'cl'
259     env['CCFLAGS']    = '/nologo'
260     env['CCCOM']      = '$CC $CCFLAGS $CCCOMFLAGS' 
261     env['SHCC']       = '$CC'
262     env['SHCCFLAGS']  = '$CCFLAGS'
263     env['SHCCCOM']    = '$SHCC $SHCCFLAGS $CCCOMFLAGS'
264     env['CXX']        = '$CC'
265     env['CXXFLAGS']   = '$CCFLAGS'
266     env['CXXCOM']     = '$CXX $CXXFLAGS $CCCOMFLAGS'
267     env['SHCXX']      = '$CXX'
268     env['SHCXXFLAGS'] = '$CXXFLAGS'
269     env['SHCXXCOM']   = '$SHCXX $SHCXXFLAGS $CCCOMFLAGS'
270     env['INCPREFIX']  = '/I'
271     env['INCSUFFIX']  = ''
272     env['OBJEMITTER'] = object_emitter
273     env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 1
274
275     env['RC'] = 'rc'
276     env['RCFLAGS'] = ''
277     env['RCCOM'] = '$RC $_CPPINCFLAGS $RCFLAGS /fo$TARGET $SOURCES'
278     env.CScan.add_skey('.rc')
279     env['BUILDERS']['RES'] = res_builder
280     
281     include_path, lib_path, exe_path = get_msdev_paths()
282     env['ENV']['INCLUDE'] = include_path
283     env['ENV']['PATH']    = exe_path
284
285     env['CFILESUFFIX'] = '.c'
286     env['CXXFILESUFFIX'] = '.cc'
287
288     env['PCHCOM'] = '$CXX $CXXFLAGS $CPPFLAGS $_CPPINCFLAGS /c $SOURCES /Fo${TARGETS[1]} /Yc$PCHSTOP /Fp${TARGETS[0]} $CCPDBFLAGS'
289     env['BUILDERS']['PCH'] = pch_builder
290
291 def exists(env):
292     return env.Detect('cl')