1#
2# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 The SCons Foundation
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__ = "src/engine/SCons/Tool/MSCommon/common.py issue-2856:2676:d23b7a2f45e8 2012/08/05 15:38:28 garyo"
25
26__doc__ = """
27Common helper functions for working with the Microsoft tool chain.
28"""
29
30import copy
31import os
32import subprocess
33import re
34
35import SCons.Util
36
37
38logfile = os.environ.get('SCONS_MSCOMMON_DEBUG')
39if logfile == '-':
40    def debug(x):
41        print x
42elif 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
50else:
51    debug = lambda x: None
52
53
54_is_win64 = None
55
56def 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
87def read_reg(value):
88    return SCons.Util.RegGetValue(SCons.Util.HKEY_LOCAL_MACHINE, value)[0]
89
90def 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
102def normalize_env(env, keys, force=False):
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    If force=True, then all of the key values that exist are copied
110    into the returned dictionary.  If force=false, values are only
111    copied if the key does not already exist in the copied dictionary.
112
113    Note: the environment is copied."""
114    normenv = {}
115    if env:
116        for k in env.keys():
117            normenv[k] = copy.deepcopy(env[k]).encode('mbcs')
118
119        for k in keys:
120            if k in os.environ and (force or not k in normenv):
121                normenv[k] = os.environ[k].encode('mbcs')
122
123    return normenv
124
125def get_output(vcbat, args = None, env = None):
126    """Parse the output of given bat file, with given args."""
127
128    if env is None:
129        # Create a blank environment, for use in launching the tools
130        env = SCons.Environment.Environment(tools=[])
131
132    # TODO:  This is a hard-coded list of the variables that (may) need
133    # to be imported from os.environ[] for v[sc]*vars*.bat file
134    # execution to work.  This list should really be either directly
135    # controlled by vc.py, or else derived from the common_tools_var
136    # settings in vs.py.
137    vars = [
138        'COMSPEC',
139        'VS90COMNTOOLS',
140        'VS80COMNTOOLS',
141        'VS71COMNTOOLS',
142        'VS70COMNTOOLS',
143        'VS60COMNTOOLS',
144    ]
145    env['ENV'] = normalize_env(env['ENV'], vars, force=False)
146
147    if args:
148        debug("Calling '%s %s'" % (vcbat, args))
149        popen = SCons.Action._subproc(env,
150                                     '"%s" %s & set' % (vcbat, args),
151                                     stdin = 'devnull',
152                                     stdout=subprocess.PIPE,
153                                     stderr=subprocess.PIPE)
154    else:
155        debug("Calling '%s'" % vcbat)
156        popen = SCons.Action._subproc(env,
157                                     '"%s" & set' % vcbat,
158                                     stdin = 'devnull',
159                                     stdout=subprocess.PIPE,
160                                     stderr=subprocess.PIPE)
161
162    # Use the .stdout and .stderr attributes directly because the
163    # .communicate() method uses the threading module on Windows
164    # and won't work under Pythons not built with threading.
165    stdout = popen.stdout.read()
166    stderr = popen.stderr.read()
167    if stderr:
168        # TODO: find something better to do with stderr;
169        # this at least prevents errors from getting swallowed.
170        import sys
171        sys.stderr.write(stderr)
172    if popen.wait() != 0:
173        raise IOError(stderr.decode("mbcs"))
174
175    output = stdout.decode("mbcs")
176    return output
177
178def parse_output(output, keep = ("INCLUDE", "LIB", "LIBPATH", "PATH")):
179    # dkeep is a dict associating key: path_list, where key is one item from
180    # keep, and pat_list the associated list of paths
181
182    dkeep = dict([(i, []) for i in keep])
183
184    # rdk will  keep the regex to match the .bat file output line starts
185    rdk = {}
186    for i in keep:
187        rdk[i] = re.compile('%s=(.*)' % i, re.I)
188
189    def add_env(rmatch, key, dkeep=dkeep):
190        plist = rmatch.group(1).split(os.pathsep)
191        for p in plist:
192            # Do not add empty paths (when a var ends with ;)
193            if p:
194                p = p.encode('mbcs')
195                # XXX: For some reason, VC98 .bat file adds "" around the PATH
196                # values, and it screws up the environment later, so we strip
197                # it.
198                p = p.strip('"')
199                dkeep[key].append(p)
200
201    for line in output.splitlines():
202        for k,v in rdk.items():
203            m = v.match(line)
204            if m:
205                add_env(m, k)
206
207    return dkeep
208
209# TODO(sgk): unused
210def output_to_dict(output):
211    """Given an output string, parse it to find env variables.
212
213    Return a dict where keys are variables names, and values their content"""
214    envlinem = re.compile(r'^([a-zA-z0-9]+)=([\S\s]*)$')
215    parsedenv = {}
216    for line in output.splitlines():
217        m = envlinem.match(line)
218        if m:
219            parsedenv[m.group(1)] = m.group(2)
220    return parsedenv
221
222# TODO(sgk): unused
223def get_new(l1, l2):
224    """Given two list l1 and l2, return the items in l2 which are not in l1.
225    Order is maintained."""
226
227    # We don't try to be smart: lists are small, and this is not the bottleneck
228    # is any case
229    new = []
230    for i in l2:
231        if i not in l1:
232            new.append(i)
233
234    return new
235
236# Local Variables:
237# tab-width:4
238# indent-tabs-mode:nil
239# End:
240# vim: set expandtab tabstop=4 shiftwidth=4:
241