You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

247 lines
8.9 KiB

6 years ago
  1. """
  2. Common helper functions for working with the Microsoft tool chain.
  3. """
  4. #
  5. # Copyright (c) 2001 - 2017 The SCons Foundation
  6. #
  7. # Permission is hereby granted, free of charge, to any person obtaining
  8. # a copy of this software and associated documentation files (the
  9. # "Software"), to deal in the Software without restriction, including
  10. # without limitation the rights to use, copy, modify, merge, publish,
  11. # distribute, sublicense, and/or sell copies of the Software, and to
  12. # permit persons to whom the Software is furnished to do so, subject to
  13. # the following conditions:
  14. #
  15. # The above copyright notice and this permission notice shall be included
  16. # in all copies or substantial portions of the Software.
  17. #
  18. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
  19. # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  20. # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  21. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  22. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  23. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  24. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  25. #
  26. from __future__ import print_function
  27. __revision__ = "src/engine/SCons/Tool/MSCommon/common.py rel_3.0.0:4395:8972f6a2f699 2017/09/18 12:59:24 bdbaddog"
  28. import copy
  29. import os
  30. import subprocess
  31. import re
  32. import SCons.Util
  33. LOGFILE = os.environ.get('SCONS_MSCOMMON_DEBUG')
  34. if LOGFILE == '-':
  35. def debug(message):
  36. print(message)
  37. elif LOGFILE:
  38. try:
  39. import logging
  40. except ImportError:
  41. debug = lambda message: open(LOGFILE, 'a').write(message + '\n')
  42. else:
  43. logging.basicConfig(filename=LOGFILE, level=logging.DEBUG)
  44. debug = logging.debug
  45. else:
  46. debug = lambda x: None
  47. _is_win64 = None
  48. def is_win64():
  49. """Return true if running on windows 64 bits.
  50. Works whether python itself runs in 64 bits or 32 bits."""
  51. # Unfortunately, python does not provide a useful way to determine
  52. # if the underlying Windows OS is 32-bit or 64-bit. Worse, whether
  53. # the Python itself is 32-bit or 64-bit affects what it returns,
  54. # so nothing in sys.* or os.* help.
  55. # Apparently the best solution is to use env vars that Windows
  56. # sets. If PROCESSOR_ARCHITECTURE is not x86, then the python
  57. # process is running in 64 bit mode (on a 64-bit OS, 64-bit
  58. # hardware, obviously).
  59. # If this python is 32-bit but the OS is 64, Windows will set
  60. # ProgramW6432 and PROCESSOR_ARCHITEW6432 to non-null.
  61. # (Checking for HKLM\Software\Wow6432Node in the registry doesn't
  62. # work, because some 32-bit installers create it.)
  63. global _is_win64
  64. if _is_win64 is None:
  65. # I structured these tests to make it easy to add new ones or
  66. # add exceptions in the future, because this is a bit fragile.
  67. _is_win64 = False
  68. if os.environ.get('PROCESSOR_ARCHITECTURE', 'x86') != 'x86':
  69. _is_win64 = True
  70. if os.environ.get('PROCESSOR_ARCHITEW6432'):
  71. _is_win64 = True
  72. if os.environ.get('ProgramW6432'):
  73. _is_win64 = True
  74. return _is_win64
  75. def read_reg(value, hkroot=SCons.Util.HKEY_LOCAL_MACHINE):
  76. return SCons.Util.RegGetValue(hkroot, value)[0]
  77. def has_reg(value):
  78. """Return True if the given key exists in HKEY_LOCAL_MACHINE, False
  79. otherwise."""
  80. try:
  81. SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE, value)
  82. ret = True
  83. except SCons.Util.WinError:
  84. ret = False
  85. return ret
  86. # Functions for fetching environment variable settings from batch files.
  87. def normalize_env(env, keys, force=False):
  88. """Given a dictionary representing a shell environment, add the variables
  89. from os.environ needed for the processing of .bat files; the keys are
  90. controlled by the keys argument.
  91. It also makes sure the environment values are correctly encoded.
  92. If force=True, then all of the key values that exist are copied
  93. into the returned dictionary. If force=false, values are only
  94. copied if the key does not already exist in the copied dictionary.
  95. Note: the environment is copied."""
  96. normenv = {}
  97. if env:
  98. for k in list(env.keys()):
  99. normenv[k] = copy.deepcopy(env[k])
  100. for k in keys:
  101. if k in os.environ and (force or not k in normenv):
  102. normenv[k] = os.environ[k]
  103. # This shouldn't be necessary, since the default environment should include system32,
  104. # but keep this here to be safe, since it's needed to find reg.exe which the MSVC
  105. # bat scripts use.
  106. sys32_dir = os.path.join(os.environ.get("SystemRoot",
  107. os.environ.get("windir", r"C:\Windows\system32")),
  108. "System32")
  109. if sys32_dir not in normenv['PATH']:
  110. normenv['PATH'] = normenv['PATH'] + os.pathsep + sys32_dir
  111. # Without Wbem in PATH, vcvarsall.bat has a "'wmic' is not recognized"
  112. # error starting with Visual Studio 2017, although the script still
  113. # seems to work anyway.
  114. sys32_wbem_dir = os.path.join(sys32_dir, 'Wbem')
  115. if sys32_wbem_dir not in normenv['PATH']:
  116. normenv['PATH'] = normenv['PATH'] + os.pathsep + sys32_wbem_dir
  117. debug("PATH: %s"%normenv['PATH'])
  118. return normenv
  119. def get_output(vcbat, args = None, env = None):
  120. """Parse the output of given bat file, with given args."""
  121. if env is None:
  122. # Create a blank environment, for use in launching the tools
  123. env = SCons.Environment.Environment(tools=[])
  124. # TODO: This is a hard-coded list of the variables that (may) need
  125. # to be imported from os.environ[] for v[sc]*vars*.bat file
  126. # execution to work. This list should really be either directly
  127. # controlled by vc.py, or else derived from the common_tools_var
  128. # settings in vs.py.
  129. vs_vc_vars = [
  130. 'COMSPEC',
  131. # VS100 and VS110: Still set, but modern MSVC setup scripts will
  132. # discard these if registry has values. However Intel compiler setup
  133. # script still requires these as of 2013/2014.
  134. 'VS140COMNTOOLS',
  135. 'VS120COMNTOOLS',
  136. 'VS110COMNTOOLS',
  137. 'VS100COMNTOOLS',
  138. 'VS90COMNTOOLS',
  139. 'VS80COMNTOOLS',
  140. 'VS71COMNTOOLS',
  141. 'VS70COMNTOOLS',
  142. 'VS60COMNTOOLS',
  143. ]
  144. env['ENV'] = normalize_env(env['ENV'], vs_vc_vars, force=False)
  145. if args:
  146. debug("Calling '%s %s'" % (vcbat, args))
  147. popen = SCons.Action._subproc(env,
  148. '"%s" %s & set' % (vcbat, args),
  149. stdin='devnull',
  150. stdout=subprocess.PIPE,
  151. stderr=subprocess.PIPE)
  152. else:
  153. debug("Calling '%s'" % vcbat)
  154. popen = SCons.Action._subproc(env,
  155. '"%s" & set' % vcbat,
  156. stdin='devnull',
  157. stdout=subprocess.PIPE,
  158. stderr=subprocess.PIPE)
  159. # Use the .stdout and .stderr attributes directly because the
  160. # .communicate() method uses the threading module on Windows
  161. # and won't work under Pythons not built with threading.
  162. stdout = popen.stdout.read()
  163. stderr = popen.stderr.read()
  164. # Extra debug logic, uncomment if necessary
  165. # debug('get_output():stdout:%s'%stdout)
  166. # debug('get_output():stderr:%s'%stderr)
  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. output = stdout.decode("mbcs")
  175. return output
  176. def parse_output(output, keep=("INCLUDE", "LIB", "LIBPATH", "PATH")):
  177. """
  178. Parse output from running visual c++/studios vcvarsall.bat and running set
  179. To capture the values listed in keep
  180. """
  181. # dkeep is a dict associating key: path_list, where key is one item from
  182. # keep, and pat_list the associated list of paths
  183. dkeep = dict([(i, []) for i in keep])
  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. def add_env(rmatch, key, dkeep=dkeep):
  189. path_list = rmatch.group(1).split(os.pathsep)
  190. for path in path_list:
  191. # Do not add empty paths (when a var ends with ;)
  192. if path:
  193. # XXX: For some reason, VC98 .bat file adds "" around the PATH
  194. # values, and it screws up the environment later, so we strip
  195. # it.
  196. path = path.strip('"')
  197. dkeep[key].append(str(path))
  198. for line in output.splitlines():
  199. for k, value in rdk.items():
  200. match = value.match(line)
  201. if match:
  202. add_env(match, k)
  203. return dkeep
  204. # Local Variables:
  205. # tab-width:4
  206. # indent-tabs-mode:nil
  207. # End:
  208. # vim: set expandtab tabstop=4 shiftwidth=4: