"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "SCons/Tool/MSCommon/common.py" between
SCons-4.3.0.tar.gz and SCons-4.4.0.tar.gz

About: SCons is a software construction tool (a Python script and a set of modules as a superior alternative to the classic "Make" build tool).

common.py  (SCons-4.3.0):common.py  (SCons-4.4.0)
""" # MIT License
Common helper functions for working with the Microsoft tool chain.
"""
# #
# __COPYRIGHT__ # Copyright The SCons Foundation
# #
# Permission is hereby granted, free of charge, to any person obtaining # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including # "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish, # without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to # distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to # permit persons to whom the Software is furnished to do so, subject to
# the following conditions: # the following conditions:
# #
# The above copyright notice and this permission notice shall be included # The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software. # in all copies or substantial portions of the Software.
# #
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" """
Common helper functions for working with the Microsoft tool chain.
"""
import copy import copy
import json import json
import os import os
import re import re
import subprocess import subprocess
import sys import sys
from contextlib import suppress
from pathlib import Path
import SCons.Util import SCons.Util
import SCons.Warnings
class MSVCCacheInvalidWarning(SCons.Warnings.WarningOnByDefault):
pass
# SCONS_MSCOMMON_DEBUG is internal-use so undocumented: # SCONS_MSCOMMON_DEBUG is internal-use so undocumented:
# set to '-' to print to console, else set to filename to log to # set to '-' to print to console, else set to filename to log to
LOGFILE = os.environ.get('SCONS_MSCOMMON_DEBUG') LOGFILE = os.environ.get('SCONS_MSCOMMON_DEBUG')
if LOGFILE == '-': if LOGFILE:
def debug(message):
print(message)
elif LOGFILE:
import logging import logging
modulelist = ( modulelist = (
# root module and parent/root module # root module and parent/root module
'MSCommon', 'Tool', 'MSCommon', 'Tool',
# python library and below: correct iff scons does not have a lib folder # python library and below: correct iff scons does not have a lib folder
'lib', 'lib',
# scons modules # scons modules
'SCons', 'test', 'scons' 'SCons', 'test', 'scons'
) )
def get_relative_filename(filename, module_list): def get_relative_filename(filename, module_list):
if not filename: if not filename:
return filename return filename
for module in module_list: for module in module_list:
try: try:
ind = filename.rindex(module) ind = filename.rindex(module)
return filename[ind:] return filename[ind:]
except ValueError: except ValueError:
pass pass
return filename return filename
class _Debug_Filter(logging.Filter): class _Debug_Filter(logging.Filter):
# custom filter for module relative filename # custom filter for module relative filename
def filter(self, record): def filter(self, record):
relfilename = get_relative_filename(record.pathname, modulelist) relfilename = get_relative_filename(record.pathname, modulelist)
relfilename = relfilename.replace('\\', '/') relfilename = relfilename.replace('\\', '/')
record.relfilename = relfilename record.relfilename = relfilename
return True return True
logging.basicConfig(
# This looks like: # Log format looks like:
# 00109ms:MSCommon/vc.py:find_vc_pdir#447: # 00109ms:MSCommon/vc.py:find_vc_pdir#447: VC found '14.3' [file]
format=( # debug: 00109ms:MSCommon/vc.py:find_vc_pdir#447: VC found '14.3' [stdout]
'%(relativeCreated)05dms' log_format=(
':%(relfilename)s' '%(relativeCreated)05dms'
':%(funcName)s' ':%(relfilename)s'
'#%(lineno)s' ':%(funcName)s'
':%(message)s: ' '#%(lineno)s'
), ': %(message)s'
filename=LOGFILE, )
level=logging.DEBUG) if LOGFILE == '-':
log_format = 'debug: ' + log_format
log_handler = logging.StreamHandler(sys.stdout)
else:
log_handler = logging.FileHandler(filename=LOGFILE)
log_formatter = logging.Formatter(log_format)
log_handler.setFormatter(log_formatter)
logger = logging.getLogger(name=__name__) logger = logging.getLogger(name=__name__)
logger.setLevel(level=logging.DEBUG)
logger.addHandler(log_handler)
logger.addFilter(_Debug_Filter()) logger.addFilter(_Debug_Filter())
debug = logger.debug debug = logger.debug
else: else:
def debug(x): return None def debug(x, *args):
return None
# SCONS_CACHE_MSVC_CONFIG is public, and is documented. # SCONS_CACHE_MSVC_CONFIG is public, and is documented.
CONFIG_CACHE = os.environ.get('SCONS_CACHE_MSVC_CONFIG') CONFIG_CACHE = os.environ.get('SCONS_CACHE_MSVC_CONFIG')
if CONFIG_CACHE in ('1', 'true', 'True'): if CONFIG_CACHE in ('1', 'true', 'True'):
CONFIG_CACHE = os.path.join(os.path.expanduser('~'), '.scons_msvc_cache') CONFIG_CACHE = os.path.join(os.path.expanduser('~'), 'scons_msvc_cache.json'
)
# SCONS_CACHE_MSVC_FORCE_DEFAULTS is internal-use so undocumented.
CONFIG_CACHE_FORCE_DEFAULT_ARGUMENTS = False
if CONFIG_CACHE:
if os.environ.get('SCONS_CACHE_MSVC_FORCE_DEFAULTS') in ('1', 'true', 'True'
):
CONFIG_CACHE_FORCE_DEFAULT_ARGUMENTS = True
def read_script_env_cache(): def read_script_env_cache():
""" fetch cached msvc env vars if requested, else return empty dict """ """ fetch cached msvc env vars if requested, else return empty dict """
envcache = {} envcache = {}
if CONFIG_CACHE: if CONFIG_CACHE:
try: try:
with open(CONFIG_CACHE, 'r') as f: p = Path(CONFIG_CACHE)
envcache = json.load(f) with p.open('r') as f:
# Convert the list of cache entry dictionaries read from
# json to the cache dictionary. Reconstruct the cache key
# tuple from the key list written to json.
envcache_list = json.load(f)
if isinstance(envcache_list, list):
envcache = {tuple(d['key']): d['data'] for d in envcache_lis
t}
else:
# don't fail if incompatible format, just proceed without it
warn_msg = "Incompatible format for msvc cache file {}: file
may be overwritten.".format(
repr(CONFIG_CACHE)
)
SCons.Warnings.warn(MSVCCacheInvalidWarning, warn_msg)
debug(warn_msg)
except FileNotFoundError: except FileNotFoundError:
# don't fail if no cache file, just proceed without it # don't fail if no cache file, just proceed without it
pass pass
return envcache return envcache
def write_script_env_cache(cache): def write_script_env_cache(cache):
""" write out cache of msvc env vars if requested """ """ write out cache of msvc env vars if requested """
if CONFIG_CACHE: if CONFIG_CACHE:
try: try:
with open(CONFIG_CACHE, 'w') as f: p = Path(CONFIG_CACHE)
json.dump(cache, f, indent=2) with p.open('w') as f:
# Convert the cache dictionary to a list of cache entry
# dictionaries. The cache key is converted from a tuple to
# a list for compatibility with json.
envcache_list = [{'key': list(key), 'data': data} for key, data
in cache.items()]
json.dump(envcache_list, f, indent=2)
except TypeError: except TypeError:
# data can't serialize to json, don't leave partial file # data can't serialize to json, don't leave partial file
os.remove(CONFIG_CACHE) with suppress(FileNotFoundError):
p.unlink()
except IOError: except IOError:
# can't write the file, just skip # can't write the file, just skip
pass pass
_is_win64 = None _is_win64 = None
def is_win64(): def is_win64():
"""Return true if running on windows 64 bits. """Return true if running on windows 64 bits.
Works whether python itself runs in 64 bits or 32 bits.""" Works whether python itself runs in 64 bits or 32 bits."""
skipping to change at line 191 skipping to change at line 231
for k in keys: for k in keys:
if k in os.environ and (force or k not in normenv): if k in os.environ and (force or k not in normenv):
normenv[k] = os.environ[k] normenv[k] = os.environ[k]
# add some things to PATH to prevent problems: # add some things to PATH to prevent problems:
# Shouldn't be necessary to add system32, since the default environment # Shouldn't be necessary to add system32, since the default environment
# should include it, but keep this here to be safe (needed for reg.exe) # should include it, but keep this here to be safe (needed for reg.exe)
sys32_dir = os.path.join( sys32_dir = os.path.join(
os.environ.get("SystemRoot", os.environ.get("windir", r"C:\Windows")), " System32" os.environ.get("SystemRoot", os.environ.get("windir", r"C:\Windows")), " System32"
) )
if sys32_dir not in normenv["PATH"]: if sys32_dir not in normenv["PATH"]:
normenv["PATH"] = normenv["PATH"] + os.pathsep + sys32_dir normenv["PATH"] = normenv["PATH"] + os.pathsep + sys32_dir
# Without Wbem in PATH, vcvarsall.bat has a "'wmic' is not recognized" # Without Wbem in PATH, vcvarsall.bat has a "'wmic' is not recognized"
# error starting with Visual Studio 2017, although the script still # error starting with Visual Studio 2017, although the script still
# seems to work anyway. # seems to work anyway.
sys32_wbem_dir = os.path.join(sys32_dir, 'Wbem') sys32_wbem_dir = os.path.join(sys32_dir, 'Wbem')
if sys32_wbem_dir not in normenv['PATH']: if sys32_wbem_dir not in normenv['PATH']:
normenv['PATH'] = normenv['PATH'] + os.pathsep + sys32_wbem_dir normenv['PATH'] = normenv['PATH'] + os.pathsep + sys32_wbem_dir
# Without Powershell in PATH, an internal call to a telemetry # Without Powershell in PATH, an internal call to a telemetry
# function (starting with a VS2019 update) can fail # function (starting with a VS2019 update) can fail
# Note can also set VSCMD_SKIP_SENDTELEMETRY to avoid this. # Note can also set VSCMD_SKIP_SENDTELEMETRY to avoid this.
sys32_ps_dir = os.path.join(sys32_dir, r'WindowsPowerShell\v1.0') sys32_ps_dir = os.path.join(sys32_dir, r'WindowsPowerShell\v1.0')
if sys32_ps_dir not in normenv['PATH']: if sys32_ps_dir not in normenv['PATH']:
normenv['PATH'] = normenv['PATH'] + os.pathsep + sys32_ps_dir normenv['PATH'] = normenv['PATH'] + os.pathsep + sys32_ps_dir
debug("PATH: %s" % normenv['PATH']) debug("PATH: %s", normenv['PATH'])
return normenv return normenv
def get_output(vcbat, args=None, env=None): def get_output(vcbat, args=None, env=None):
"""Parse the output of given bat file, with given args.""" """Parse the output of given bat file, with given args."""
if env is None: if env is None:
# Create a blank environment, for use in launching the tools # Create a blank environment, for use in launching the tools
env = SCons.Environment.Environment(tools=[]) env = SCons.Environment.Environment(tools=[])
# TODO: Hard-coded list of the variables that (may) need to be # TODO: Hard-coded list of the variables that (may) need to be
skipping to change at line 232 skipping to change at line 272
# end up running a dozen or more scripts, changes not only with # end up running a dozen or more scripts, changes not only with
# each release but with what is installed at the time. We think # each release but with what is installed at the time. We think
# in modern installations most are set along the way and don't # in modern installations most are set along the way and don't
# need to be picked from the env, but include these for safety's sake. # need to be picked from the env, but include these for safety's sake.
# Any VSCMD variables definitely are picked from the env and # Any VSCMD variables definitely are picked from the env and
# control execution in interesting ways. # control execution in interesting ways.
# Note these really should be unified - either controlled by vs.py, # Note these really should be unified - either controlled by vs.py,
# or synced with the the common_tools_var # settings in vs.py. # or synced with the the common_tools_var # settings in vs.py.
vs_vc_vars = [ vs_vc_vars = [
'COMSPEC', # path to "shell" 'COMSPEC', # path to "shell"
'OS', # name of OS family: Windows_NT or undefined (95/98/ME)
'VS170COMNTOOLS', # path to common tools for given version 'VS170COMNTOOLS', # path to common tools for given version
'VS160COMNTOOLS', 'VS160COMNTOOLS',
'VS150COMNTOOLS', 'VS150COMNTOOLS',
'VS140COMNTOOLS', 'VS140COMNTOOLS',
'VS120COMNTOOLS', 'VS120COMNTOOLS',
'VS110COMNTOOLS', 'VS110COMNTOOLS',
'VS100COMNTOOLS', 'VS100COMNTOOLS',
'VS90COMNTOOLS', 'VS90COMNTOOLS',
'VS80COMNTOOLS', 'VS80COMNTOOLS',
'VS71COMNTOOLS', 'VS71COMNTOOLS',
'VS70COMNTOOLS', 'VSCOMNTOOLS',
'VS60COMNTOOLS', 'MSDevDir',
'VSCMD_DEBUG', # enable logging and other debug aids 'VSCMD_DEBUG', # enable logging and other debug aids
'VSCMD_SKIP_SENDTELEMETRY', 'VSCMD_SKIP_SENDTELEMETRY',
'windir', # windows directory (SystemRoot not available in 95/98/ME)
] ]
env['ENV'] = normalize_env(env['ENV'], vs_vc_vars, force=False) env['ENV'] = normalize_env(env['ENV'], vs_vc_vars, force=False)
if args: if args:
debug("Calling '%s %s'" % (vcbat, args)) debug("Calling '%s %s'", vcbat, args)
popen = SCons.Action._subproc(env, popen = SCons.Action._subproc(env,
'"%s" %s & set' % (vcbat, args), '"%s" %s & set' % (vcbat, args),
stdin='devnull', stdin='devnull',
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE) stderr=subprocess.PIPE)
else: else:
debug("Calling '%s'" % vcbat) debug("Calling '%s'", vcbat)
popen = SCons.Action._subproc(env, popen = SCons.Action._subproc(env,
'"%s" & set' % vcbat, '"%s" & set' % vcbat,
stdin='devnull', stdin='devnull',
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE) stderr=subprocess.PIPE)
# Use the .stdout and .stderr attributes directly because the # Use the .stdout and .stderr attributes directly because the
# .communicate() method uses the threading module on Windows # .communicate() method uses the threading module on Windows
# and won't work under Pythons not built with threading. # and won't work under Pythons not built with threading.
with popen.stdout: with popen.stdout:
stdout = popen.stdout.read() stdout = popen.stdout.read()
with popen.stderr: with popen.stderr:
stderr = popen.stderr.read() stderr = popen.stderr.read()
# Extra debug logic, uncomment if necessary # Extra debug logic, uncomment if necessary
# debug('stdout:%s' % stdout) # debug('stdout:%s', stdout)
# debug('stderr:%s' % stderr) # debug('stderr:%s', stderr)
# Ongoing problems getting non-corrupted text led to this # Ongoing problems getting non-corrupted text led to this
# changing to "oem" from "mbcs" - the scripts run presumably # changing to "oem" from "mbcs" - the scripts run presumably
# attached to a console, so some particular rules apply. # attached to a console, so some particular rules apply.
# Unfortunately, "oem" not defined in Python 3.5, so get another way # Unfortunately, "oem" not defined in Python 3.5, so get another way
if sys.version_info.major == 3 and sys.version_info.minor < 6: if sys.version_info.major == 3 and sys.version_info.minor < 6:
from ctypes import windll from ctypes import windll
OEM = "cp{}".format(windll.kernel32.GetConsoleOutputCP()) OEM = "cp{}".format(windll.kernel32.GetConsoleOutputCP())
else: else:
skipping to change at line 313 skipping to change at line 355
) )
def parse_output(output, keep=KEEPLIST): def parse_output(output, keep=KEEPLIST):
""" """
Parse output from running visual c++/studios vcvarsall.bat and running set Parse output from running visual c++/studios vcvarsall.bat and running set
To capture the values listed in keep To capture the values listed in keep
""" """
# dkeep is a dict associating key: path_list, where key is one item from # dkeep is a dict associating key: path_list, where key is one item from
# keep, and path_list the associated list of paths # keep, and path_list the associated list of paths
dkeep = dict([(i, []) for i in keep]) dkeep = {i: [] for i in keep}
# rdk will keep the regex to match the .bat file output line starts # rdk will keep the regex to match the .bat file output line starts
rdk = {} rdk = {}
for i in keep: for i in keep:
rdk[i] = re.compile('%s=(.*)' % i, re.I) rdk[i] = re.compile('%s=(.*)' % i, re.I)
def add_env(rmatch, key, dkeep=dkeep): def add_env(rmatch, key, dkeep=dkeep):
path_list = rmatch.group(1).split(os.pathsep) path_list = rmatch.group(1).split(os.pathsep)
for path in path_list: for path in path_list:
# Do not add empty paths (when a var ends with ;) # Do not add empty paths (when a var ends with ;)
 End of changes. 25 change blocks. 
38 lines changed or deleted 85 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)