diff options
Diffstat (limited to 'engine/SCons/Tool/MSCommon/vc.py')
| -rw-r--r-- | engine/SCons/Tool/MSCommon/vc.py | 328 | 
1 files changed, 265 insertions, 63 deletions
diff --git a/engine/SCons/Tool/MSCommon/vc.py b/engine/SCons/Tool/MSCommon/vc.py index 4139ee3..1f1332e 100644 --- a/engine/SCons/Tool/MSCommon/vc.py +++ b/engine/SCons/Tool/MSCommon/vc.py @@ -1,5 +1,5 @@  # -# Copyright (c) 2001 - 2017 The SCons Foundation +# Copyright (c) 2001 - 2019 The SCons Foundation  #  # Permission is hereby granted, free of charge, to any person obtaining  # a copy of this software and associated documentation files (the @@ -30,7 +30,7 @@  #   * test on 64 bits XP +  VS 2005 (and VS 6 if possible)  #   * SDK  #   * Assembly -__revision__ = "src/engine/SCons/Tool/MSCommon/vc.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" +__revision__ = "src/engine/SCons/Tool/MSCommon/vc.py a56bbd8c09fb219ab8a9673330ffcd55279219d0 2019-03-26 23:16:31 bdeegan"  __doc__ = """Module for Visual C/C++ detection and configuration.  """ @@ -43,6 +43,7 @@ import platform  from string import digits as string_digits  import SCons.Warnings +from SCons.Tool import find_program_path  from . import common @@ -79,15 +80,43 @@ _ARCH_TO_CANONICAL = {      "i486"      : "x86",      "i586"      : "x86",      "i686"      : "x86", -    "ia64"      : "ia64", -    "itanium"   : "ia64", +    "ia64"      : "ia64",      # deprecated +    "itanium"   : "ia64",      # deprecated      "x86"       : "x86",      "x86_64"    : "amd64", -    "x86_amd64" : "x86_amd64", # Cross compile to 64 bit from 32bits +    "arm"       : "arm", +    "arm64"     : "arm64", +    "aarch64"   : "arm64",  } -# Given a (host, target) tuple, return the argument for the bat file. Both host -# and targets should be canonalized. +# get path to the cl.exe dir for newer VS versions +# based off a tuple of (host, target) platforms +_HOST_TARGET_TO_CL_DIR_GREATER_THAN_14 = { +    ("amd64","amd64")  : "Hostx64\\x64", +    ("amd64","x86")    : "Hostx64\\x86", +    ("amd64","arm")    : "Hostx64\\arm", +    ("amd64","arm64")  : "Hostx64\\arm64", +    ("x86","amd64")    : "Hostx86\\x64", +    ("x86","x86")      : "Hostx86\\x86", +    ("x86","arm")      : "Hostx86\\arm", +    ("x86","arm64")    : "Hostx86\\arm64", +} + +# get path to the cl.exe dir for older VS versions +# based off a tuple of (host, target) platforms +_HOST_TARGET_TO_CL_DIR = { +    ("amd64","amd64")  : "amd64", +    ("amd64","x86")    : "amd64_x86", +    ("amd64","arm")    : "amd64_arm", +    ("amd64","arm64")  : "amd64_arm64", +    ("x86","amd64")    : "x86_amd64", +    ("x86","x86")      : "", +    ("x86","arm")      : "x86_arm", +    ("x86","arm64")    : "x86_arm64", +} + +# Given a (host, target) tuple, return the argument for the bat file. +# Both host and targets should be canonalized.  _HOST_TARGET_ARCH_TO_BAT_ARCH = {      ("x86", "x86"): "x86",      ("x86", "amd64"): "x86_amd64", @@ -95,9 +124,32 @@ _HOST_TARGET_ARCH_TO_BAT_ARCH = {      ("amd64", "x86_amd64"): "x86_amd64", # This is present in (at least) VS2012 express      ("amd64", "amd64"): "amd64",      ("amd64", "x86"): "x86", -    ("x86", "ia64"): "x86_ia64" +    ("x86", "ia64"): "x86_ia64",         # gone since 14.0 +    ("arm", "arm"): "arm",              # since 14.0, maybe gone 14.1? +    ("x86", "arm"): "x86_arm",          # since 14.0 +    ("x86", "arm64"): "x86_arm64",      # since 14.1 +    ("amd64", "arm"): "amd64_arm",      # since 14.0 +    ("amd64", "arm64"): "amd64_arm64",  # since 14.1  } +_CL_EXE_NAME = 'cl.exe' + +def get_msvc_version_numeric(msvc_version): +    """Get the raw version numbers from a MSVC_VERSION string, so it +    could be cast to float or other numeric values. For example, '14.0Exp' +    would get converted to '14.0'. + +    Args: +        msvc_version: str +            string representing the version number, could contain non +            digit characters + +    Returns: +        str: the value converted to a numeric only string + +    """ +    return ''.join([x for  x in msvc_version if x in string_digits + '.']) +  def get_host_target(env):      debug('vc.py:get_host_target()') @@ -188,7 +240,7 @@ _VCVER_TO_PRODUCT_DIR = {  }  def msvc_version_to_maj_min(msvc_version): -    msvc_version_numeric = ''.join([x for  x in msvc_version if x in string_digits + '.']) +    msvc_version_numeric = get_msvc_version_numeric(msvc_version)      t = msvc_version_numeric.split(".")      if not len(t) == 2: @@ -201,21 +253,21 @@ def msvc_version_to_maj_min(msvc_version):          raise ValueError("Unrecognized version %s (%s)" % (msvc_version,msvc_version_numeric))  def is_host_target_supported(host_target, msvc_version): -    """Return True if the given (host, target) tuple is supported given the -    msvc version. - -    Parameters -    ---------- -    host_target: tuple -        tuple of (canonalized) host-target, e.g. ("x86", "amd64") for cross -        compilation from 32 bits windows to 64 bits. -    msvc_version: str -        msvc version (major.minor, e.g. 10.0) - -    Note -    ---- -    This only check whether a given version *may* support the given (host, -    target), not that the toolchain is actually present on the machine. +    """Check if the given (host, target) tuple is supported for given version. + +    Args: +        host_target: tuple +            tuple of (canonalized) host-targets, e.g. ("x86", "amd64") +            for cross compilation from 32 bit Windows to 64 bits. +        msvc_version: str +            msvc version (major.minor, e.g. 10.0) + +    Returns: +        bool: + +    Note: +        This only checks whether a given version *may* support the given (host, +        target), not that the toolchain is actually present on the machine.      """      # We assume that any Visual Studio version supports x86 as a target      if host_target[1] != "x86": @@ -228,40 +280,69 @@ def is_host_target_supported(host_target, msvc_version):  def find_vc_pdir_vswhere(msvc_version):      """ -    Find the MSVC product directory using vswhere.exe . +    Find the MSVC product directory using vswhere.exe. +      Run it asking for specified version and get MSVS  install location      :param msvc_version: -    :return: MSVC install dir +    :return: MSVC install dir or None      """ -    vswhere_path = os.path.join( -        'C:\\', -        'Program Files (x86)', -        'Microsoft Visual Studio', -        'Installer', -        'vswhere.exe' -    ) -    vswhere_cmd = [vswhere_path, '-version', msvc_version, '-property', 'installationPath'] + +    # For bug 3333 - support default location of vswhere for both 64 and 32 bit windows +    # installs.  +    for pf in ['Program Files (x86)', 'Program Files']: +        vswhere_path = os.path.join( +            'C:\\', +            pf, +            'Microsoft Visual Studio', +            'Installer', +            'vswhere.exe' +        ) +        if os.path.exists(vswhere_path): +            # If we found vswhere, then use it. +            break + +    vswhere_cmd = [vswhere_path, '-products', '*', '-version', msvc_version, '-property', 'installationPath']      if os.path.exists(vswhere_path): -        sp = subprocess.Popen(vswhere_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) +        #TODO PY27 cannot use Popen as context manager +        # try putting it back to the old way for now +        sp = subprocess.Popen(vswhere_cmd, +                              stdout=subprocess.PIPE, +                              stderr=subprocess.PIPE)          vsdir, err = sp.communicate() -        vsdir = vsdir.decode("mbcs") -        vsdir = vsdir.rstrip() -        vc_pdir = os.path.join(vsdir, 'VC') -        return vc_pdir +        if vsdir: +            vsdir = vsdir.decode("mbcs").splitlines() +            # vswhere could easily return multiple lines +            # we could define a way to pick the one we prefer, but since +            # this data is currently only used to make a check for existence, +            # returning the first hit should be good enough for now. +            vc_pdir = os.path.join(vsdir[0], 'VC') +            return vc_pdir      else:          # No vswhere on system, no install info available          return None  def find_vc_pdir(msvc_version): -    """Try to find the product directory for the given -    version. +    """Find the product directory for the given version. + +    Tries to look up the path using a registry key from the table +    _VCVER_TO_PRODUCT_DIR; if there is no key, calls find_vc_pdir_wshere +    for help instead. + +    Args: +        msvc_version: str +            msvc version (major.minor, e.g. 10.0) + +    Returns: +        str: Path found in registry, or None -    Note -    ---- -    If for some reason the requested version could not be found, an -    exception which inherits from VisualCException will be raised.""" +    Raises: +        UnsupportedVersion: if the version is not known by this file. +        MissingConfiguration: found version but the directory is missing. + +        Both exceptions inherit from VisualCException. +    """      root = 'Software\\'      try:          hkeys = _VCVER_TO_PRODUCT_DIR[msvc_version] @@ -275,8 +356,10 @@ def find_vc_pdir(msvc_version):              if not key:                  comps = find_vc_pdir_vswhere(msvc_version)                  if not comps: -                    debug('find_vc_dir(): no VC found via vswhere for version {}'.format(repr(key))) +                    debug('find_vc_pdir_vswhere(): no VC found for version {}'.format(repr(msvc_version)))                      raise SCons.Util.WinError +                debug('find_vc_pdir_vswhere(): VC found: {}'.format(repr(msvc_version))) +                return comps              else:                  if common.is_win64():                      try: @@ -308,10 +391,10 @@ def find_batch_file(env,msvc_version,host_arch,target_arch):      if pdir is None:          raise NoVersionFound("No version of Visual Studio found") -    debug('vc.py: find_batch_file() pdir:{}'.format(pdir)) +    debug('vc.py: find_batch_file() in {}'.format(pdir))      # filter out e.g. "Exp" from the version name -    msvc_ver_numeric = ''.join([x for x in msvc_version if x in string_digits + "."]) +    msvc_ver_numeric = get_msvc_version_numeric(msvc_version)      vernum = float(msvc_ver_numeric)      if 7 <= vernum < 8:          pdir = os.path.join(pdir, os.pardir, "Common7", "Tools") @@ -343,23 +426,132 @@ def find_batch_file(env,msvc_version,host_arch,target_arch):  __INSTALLED_VCS_RUN = None -def cached_get_installed_vcs(): +def _check_cl_exists_in_vc_dir(env, vc_dir, msvc_version): +    """Find the cl.exe on the filesystem in the vc_dir depending on +    TARGET_ARCH, HOST_ARCH and the msvc version. TARGET_ARCH and +    HOST_ARCH can be extracted from the passed env, unless its None, +    which then the native platform is assumed the host and target. + +    Args: +        env: Environment +            a construction environment, usually if this is passed its +            because there is a desired TARGET_ARCH to be used when searching +            for a cl.exe +        vc_dir: str +            the path to the VC dir in the MSVC installation +        msvc_version: str +            msvc version (major.minor, e.g. 10.0) + +    Returns: +        bool: + +    """ + +    # determine if there is a specific target platform we want to build for and +    # use that to find a list of valid VCs, default is host platform == target platform +    # and same for if no env is specified to extract target platform from +    if env: +        (host_platform, target_platform, req_target_platform) = get_host_target(env) +    else: +        host_platform = platform.machine().lower() +        target_platform = host_platform + +    host_platform = _ARCH_TO_CANONICAL[host_platform] +    target_platform = _ARCH_TO_CANONICAL[target_platform] + +    debug('_check_cl_exists_in_vc_dir(): host platform %s, target platform %s' % (host_platform, target_platform)) + +    ver_num = float(get_msvc_version_numeric(msvc_version)) + +    # make sure the cl.exe exists meaning the tool is installed +    if ver_num > 14: +        # 2017 and newer allowed multiple versions of the VC toolset to be installed at the same time. +        # Just get the default tool version for now +        #TODO: support setting a specific minor VC version +        default_toolset_file = os.path.join(vc_dir, r'Auxiliary\Build\Microsoft.VCToolsVersion.default.txt') +        try: +            with open(default_toolset_file) as f: +                vc_specific_version = f.readlines()[0].strip() +        except IOError: +            debug('_check_cl_exists_in_vc_dir(): failed to read ' + default_toolset_file) +            return False +        except IndexError: +            debug('_check_cl_exists_in_vc_dir(): failed to find MSVC version in ' + default_toolset_file) +            return False + +        host_trgt_dir = _HOST_TARGET_TO_CL_DIR_GREATER_THAN_14.get((host_platform, target_platform), None) +        if not host_trgt_dir: +            debug('_check_cl_exists_in_vc_dir(): unsupported host/target platform combo') +            return False + +        cl_path = os.path.join(vc_dir, r'Tools\MSVC', vc_specific_version, 'bin', host_trgt_dir, _CL_EXE_NAME) +        debug('_check_cl_exists_in_vc_dir(): checking for ' + _CL_EXE_NAME + ' at ' + cl_path) +        if os.path.exists(cl_path): +            debug('_check_cl_exists_in_vc_dir(): found ' + _CL_EXE_NAME + '!') +            return True + +    elif ver_num <= 14 and ver_num >= 8: + +        host_trgt_dir = _HOST_TARGET_TO_CL_DIR.get((host_platform, target_platform), None) +        if not host_trgt_dir: +            debug('_check_cl_exists_in_vc_dir(): unsupported host/target platform combo') +            return False + +        cl_path = os.path.join(vc_dir, 'bin',  host_trgt_dir, _CL_EXE_NAME) +        debug('_check_cl_exists_in_vc_dir(): checking for ' + _CL_EXE_NAME + ' at ' + cl_path) + +        cl_path_exists = os.path.exists(cl_path) +        if not cl_path_exists and host_platform == 'amd64': +            # older versions of visual studio only had x86 binaries, +            # so if the host platform is amd64, we need to check cross +            # compile options (x86 binary compiles some other target on a 64 bit os) +            host_trgt_dir = _HOST_TARGET_TO_CL_DIR.get(('x86', target_platform), None) +            if not host_trgt_dir: +                return False + +            cl_path = os.path.join(vc_dir, 'bin', host_trgt_dir, _CL_EXE_NAME) +            debug('_check_cl_exists_in_vc_dir(): checking for ' + _CL_EXE_NAME + ' at ' + cl_path) +            cl_path_exists = os.path.exists(cl_path) + +        if cl_path_exists: +            debug('_check_cl_exists_in_vc_dir(): found ' + _CL_EXE_NAME + '!') +            return True + +    elif ver_num < 8 and ver_num >= 6: +        # not sure about these versions so if a walk the VC dir (could be slow) +        for root, _, files in os.walk(vc_dir): +            if _CL_EXE_NAME in files: +                debug('get_installed_vcs ' + _CL_EXE_NAME + ' found %s' % os.path.join(root, _CL_EXE_NAME)) +                return True +        return False +    else: +        # version not support return false +        debug('_check_cl_exists_in_vc_dir(): unsupported MSVC version: ' + str(ver_num)) + +    return False + +def cached_get_installed_vcs(env=None):      global __INSTALLED_VCS_RUN      if __INSTALLED_VCS_RUN is None: -        ret = get_installed_vcs() +        ret = get_installed_vcs(env)          __INSTALLED_VCS_RUN = ret      return __INSTALLED_VCS_RUN -def get_installed_vcs(): +def get_installed_vcs(env=None):      installed_versions = [] +      for ver in _VCVER:          debug('trying to find VC %s' % ver)          try: -            if find_vc_pdir(ver): +            VC_DIR = find_vc_pdir(ver) +            if VC_DIR:                  debug('found VC %s' % ver) -                installed_versions.append(ver) +                if _check_cl_exists_in_vc_dir(env, VC_DIR, ver): +                    installed_versions.append(ver) +                else: +                    debug('find_vc_pdir no compiler found %s' % ver)              else:                  debug('find_vc_pdir return None for ver %s' % ver)          except VisualCException as e: @@ -416,7 +608,7 @@ def get_default_version(env):                      % (msvc_version, msvs_version))          return msvs_version      if not msvc_version: -        installed_vcs = cached_get_installed_vcs() +        installed_vcs = cached_get_installed_vcs(env)          debug('installed_vcs:%s' % installed_vcs)          if not installed_vcs:              #msg = 'No installed VCs' @@ -443,16 +635,17 @@ def msvc_find_valid_batch_script(env,version):      debug('vc.py:msvc_find_valid_batch_script()')      # Find the host platform, target platform, and if present the requested      # target platform -    (host_platform, target_platform,req_target_platform) = get_host_target(env) +    platforms = get_host_target(env) +    debug("vc.py: msvs_find_valid_batch_script(): host_platform %s, target_platform %s req_target_platform:%s" % platforms) +    host_platform, target_platform, req_target_platform = platforms      try_target_archs = [target_platform] -    debug("msvs_find_valid_batch_script(): req_target_platform %s target_platform:%s"%(req_target_platform,target_platform))      # VS2012 has a "cross compile" environment to build 64 bit      # with x86_amd64 as the argument to the batch setup script -    if req_target_platform in ('amd64','x86_64'): +    if req_target_platform in ('amd64', 'x86_64'):          try_target_archs.append('x86_amd64') -    elif not req_target_platform and target_platform in ['amd64','x86_64']: +    elif not req_target_platform and target_platform in ['amd64', 'x86_64']:          # There may not be "native" amd64, but maybe "cross" x86_amd64 tools          try_target_archs.append('x86_amd64')          # If the user hasn't specifically requested a TARGET_ARCH, and @@ -474,7 +667,7 @@ def msvc_find_valid_batch_script(env,version):                  (host_target, version)              SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg)          arg = _HOST_TARGET_ARCH_TO_BAT_ARCH[host_target] -         +          # Get just version numbers          maj, min = msvc_version_to_maj_min(version)          # VS2015+ @@ -493,15 +686,17 @@ def msvc_find_valid_batch_script(env,version):              warn_msg = "VC version %s not installed.  " + \                         "C/C++ compilers are most likely not set correctly.\n" + \                         " Installed versions are: %s" -            warn_msg = warn_msg % (version, cached_get_installed_vcs()) +            warn_msg = warn_msg % (version, cached_get_installed_vcs(env))              SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg)              continue          # Try to use the located batch file for this host/target platform combo          debug('vc.py:msvc_find_valid_batch_script() use_script 2 %s, args:%s\n' % (repr(vc_script), arg)) +        found = None          if vc_script:              try:                  d = script_env(vc_script, args=arg) +                found = vc_script              except BatchFileExecutionError as e:                  debug('vc.py:msvc_find_valid_batch_script() use_script 3: failed running VC script %s: %s: Error:%s'%(repr(vc_script),arg,e))                  vc_script=None @@ -510,6 +705,7 @@ def msvc_find_valid_batch_script(env,version):              debug('vc.py:msvc_find_valid_batch_script() use_script 4: trying sdk script: %s'%(sdk_script))              try:                  d = script_env(sdk_script) +                found = sdk_script              except BatchFileExecutionError as e:                  debug('vc.py:msvc_find_valid_batch_script() use_script 5: failed running SDK script %s: Error:%s'%(repr(sdk_script),e))                  continue @@ -517,7 +713,7 @@ def msvc_find_valid_batch_script(env,version):              debug('vc.py:msvc_find_valid_batch_script() use_script 6: Neither VC script nor SDK script found')              continue -        debug("vc.py:msvc_find_valid_batch_script() Found a working script/target: %s %s"%(repr(sdk_script),arg)) +        debug("vc.py:msvc_find_valid_batch_script() Found a working script/target: %s/%s"%(repr(found),arg))          break # We've found a working target_platform, so stop looking      # If we cannot find a viable installed compiler, reset the TARGET_ARCH @@ -566,8 +762,14 @@ def msvc_setup_env(env):          debug('vc.py:msvc_setup_env() env:%s -> %s'%(k,v))          env.PrependENVPath(k, v, delete_existing=True) -def msvc_exists(version=None): -    vcs = cached_get_installed_vcs() +    # final check to issue a warning if the compiler is not present +    msvc_cl = find_program_path(env, 'cl') +    if not msvc_cl: +        SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, +            "Could not find MSVC compiler 'cl', it may need to be installed separately with Visual Studio") + +def msvc_exists(env=None, version=None): +    vcs = cached_get_installed_vcs(env)      if version is None:          return len(vcs) > 0      return version in vcs  | 
