Move 2.0 changes collected in branches/pending back to trunk for further
[scons.git] / src / engine / SCons / Tool / MSCommon / common.py
1 #
2 # __COPYRIGHT__
3 #
4 # Permission is hereby granted, free of charge, to any person obtaining
5 # a copy of this software and associated documentation files (the
6 # "Software"), to deal in the Software without restriction, including
7 # without limitation the rights to use, copy, modify, merge, publish,
8 # distribute, sublicense, and/or sell copies of the Software, and to
9 # permit persons to whom the Software is furnished to do so, subject to
10 # the following conditions:
11 #
12 # The above copyright notice and this permission notice shall be included
13 # in all copies or substantial portions of the Software.
14 #
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
16 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
17 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 #
23
24 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
25
26 __doc__ = """
27 Common helper functions for working with the Microsoft tool chain.
28 """
29
30 import copy
31 import os
32 import subprocess
33 import re
34
35 import SCons.Util
36
37
38 logfile = os.environ.get('SCONS_MSCOMMON_DEBUG')
39 if logfile == '-':
40     def debug(x):
41         print x
42 elif logfile:
43     try:
44         import logging
45     except ImportError:
46         debug = lambda x: open(logfile, 'a').write(x + '\n')
47     else:
48         logging.basicConfig(filename=logfile, level=logging.DEBUG)
49         debug = logging.debug
50 else:
51     debug = lambda x: None
52
53
54 _is_win64 = None
55
56 def is_win64():
57     """Return true if running on windows 64 bits.
58     
59     Works whether python itself runs in 64 bits or 32 bits."""
60     # Unfortunately, python does not provide a useful way to determine
61     # if the underlying Windows OS is 32-bit or 64-bit.  Worse, whether
62     # the Python itself is 32-bit or 64-bit affects what it returns,
63     # so nothing in sys.* or os.* help.  
64
65     # Apparently the best solution is to use env vars that Windows
66     # sets.  If PROCESSOR_ARCHITECTURE is not x86, then the python
67     # process is running in 64 bit mode (on a 64-bit OS, 64-bit
68     # hardware, obviously).
69     # If this python is 32-bit but the OS is 64, Windows will set
70     # ProgramW6432 and PROCESSOR_ARCHITEW6432 to non-null.
71     # (Checking for HKLM\Software\Wow6432Node in the registry doesn't
72     # work, because some 32-bit installers create it.)
73     global _is_win64
74     if _is_win64 is None:
75         # I structured these tests to make it easy to add new ones or
76         # add exceptions in the future, because this is a bit fragile.
77         _is_win64 = False
78         if os.environ.get('PROCESSOR_ARCHITECTURE','x86') != 'x86':
79             _is_win64 = True
80         if os.environ.get('PROCESSOR_ARCHITEW6432'):
81             _is_win64 = True
82         if os.environ.get('ProgramW6432'):
83             _is_win64 = True
84     return _is_win64
85
86
87 def read_reg(value):
88     return SCons.Util.RegGetValue(SCons.Util.HKEY_LOCAL_MACHINE, value)[0]
89
90 def has_reg(value):
91     """Return True if the given key exists in HKEY_LOCAL_MACHINE, False
92     otherwise."""
93     try:
94         SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE, value)
95         ret = True
96     except WindowsError:
97         ret = False
98     return ret
99
100 # Functions for fetching environment variable settings from batch files.
101
102 def normalize_env(env, keys):
103     """Given a dictionary representing a shell environment, add the variables
104     from os.environ needed for the processing of .bat files; the keys are
105     controlled by the keys argument.
106
107     It also makes sure the environment values are correctly encoded.
108
109     Note: the environment is copied"""
110     normenv = {}
111     if env:
112         for k in env.keys():
113             normenv[k] = copy.deepcopy(env[k]).encode('mbcs')
114
115         for k in keys:
116             if k in os.environ:
117                 normenv[k] = os.environ[k].encode('mbcs')
118
119     return normenv
120
121 def get_output(vcbat, args = None, env = None):
122     """Parse the output of given bat file, with given args."""
123     if args:
124         debug("Calling '%s %s'" % (vcbat, args))
125         popen = subprocess.Popen('"%s" %s & set' % (vcbat, args),
126                                  stdout=subprocess.PIPE,
127                                  stderr=subprocess.PIPE,
128                                  env=env)
129     else:
130         debug("Calling '%s'" % vcbat)
131         popen = subprocess.Popen('"%s" & set' % vcbat,
132                                  stdout=subprocess.PIPE,
133                                  stderr=subprocess.PIPE,
134                                  env=env)
135
136     # Use the .stdout and .stderr attributes directly because the
137     # .communicate() method uses the threading module on Windows
138     # and won't work under Pythons not built with threading.
139     stdout = popen.stdout.read()
140     if popen.wait() != 0:
141         raise IOError(popen.stderr.read().decode("mbcs"))
142
143     output = stdout.decode("mbcs")
144     return output
145
146 def parse_output(output, keep = ("INCLUDE", "LIB", "LIBPATH", "PATH")):
147     # dkeep is a dict associating key: path_list, where key is one item from
148     # keep, and pat_list the associated list of paths
149
150     # TODO(1.5):  replace with the following list comprehension:
151     #dkeep = dict([(i, []) for i in keep])
152     dkeep = dict([(i, []) for i in keep])
153
154     # rdk will  keep the regex to match the .bat file output line starts
155     rdk = {}
156     for i in keep:
157         rdk[i] = re.compile('%s=(.*)' % i, re.I)
158
159     def add_env(rmatch, key, dkeep=dkeep):
160         plist = rmatch.group(1).split(os.pathsep)
161         for p in plist:
162             # Do not add empty paths (when a var ends with ;)
163             if p:
164                 p = p.encode('mbcs')
165                 # XXX: For some reason, VC98 .bat file adds "" around the PATH
166                 # values, and it screws up the environment later, so we strip
167                 # it. 
168                 p = p.strip('"')
169                 dkeep[key].append(p)
170
171     for line in output.splitlines():
172         for k,v in rdk.items():
173             m = v.match(line)
174             if m:
175                 add_env(m, k)
176
177     return dkeep
178
179 # TODO(sgk): unused
180 def output_to_dict(output):
181     """Given an output string, parse it to find env variables.
182
183     Return a dict where keys are variables names, and values their content"""
184     envlinem = re.compile(r'^([a-zA-z0-9]+)=([\S\s]*)$')
185     parsedenv = {}
186     for line in output.splitlines():
187         m = envlinem.match(line)
188         if m:
189             parsedenv[m.group(1)] = m.group(2)
190     return parsedenv
191
192 # TODO(sgk): unused
193 def get_new(l1, l2):
194     """Given two list l1 and l2, return the items in l2 which are not in l1.
195     Order is maintained."""
196
197     # We don't try to be smart: lists are small, and this is not the bottleneck
198     # is any case
199     new = []
200     for i in l2:
201         if i not in l1:
202             new.append(i)
203
204     return new
205
206 # Local Variables:
207 # tab-width:4
208 # indent-tabs-mode:nil
209 # End:
210 # vim: set expandtab tabstop=4 shiftwidth=4: