vc.py 19.8 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527
#
# Copyright (c) 2001 - 2015 The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

# TODO:
#   * supported arch for versions: for old versions of batch file without
#     argument, giving bogus argument cannot be detected, so we have to hardcode
#     this here
#   * print warning when msvc version specified but not found
#   * find out why warning do not print
#   * test on 64 bits XP +  VS 2005 (and VS 6 if possible)
#   * SDK
#   * Assembly
__revision__ = "src/engine/SCons/Tool/MSCommon/vc.py rel_2.4.1:3453:73fefd3ea0b0 2015/11/09 03:25:05 bdbaddog"

__doc__ = """Module for Visual C/C++ detection and configuration.
"""
import SCons.compat
import SCons.Util

import os
import platform
from string import digits as string_digits

import SCons.Warnings

import common

debug = common.debug

import sdk

get_installed_sdks = sdk.get_installed_sdks


class VisualCException(Exception):
    pass

class UnsupportedVersion(VisualCException):
    pass

class UnsupportedArch(VisualCException):
    pass

class MissingConfiguration(VisualCException):
    pass

class NoVersionFound(VisualCException):
    pass

class BatchFileExecutionError(VisualCException):
    pass

# Dict to 'canonalize' the arch
_ARCH_TO_CANONICAL = {
    "amd64"     : "amd64",
    "emt64"     : "amd64",
    "i386"      : "x86",
    "i486"      : "x86",
    "i586"      : "x86",
    "i686"      : "x86",
    "ia64"      : "ia64",
    "itanium"   : "ia64",
    "x86"       : "x86",
    "x86_64"    : "amd64",
    "x86_amd64" : "x86_amd64", # Cross compile to 64 bit from 32bits
}

# 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",
    ("x86", "x86_amd64"): "x86_amd64",
    ("amd64", "x86_amd64"): "x86_amd64", # This is present in (at least) VS2012 express
    ("amd64", "amd64"): "amd64",
    ("amd64", "x86"): "x86",
    ("x86", "ia64"): "x86_ia64"
}

def get_host_target(env):
    debug('vc.py:get_host_target()')

    host_platform = env.get('HOST_ARCH')
    if not host_platform:
        host_platform = platform.machine()
        # TODO(2.5):  the native Python platform.machine() function returns
        # '' on all Python versions before 2.6, after which it also uses
        # PROCESSOR_ARCHITECTURE.
        if not host_platform:
            host_platform = os.environ.get('PROCESSOR_ARCHITECTURE', '')
            
    # Retain user requested TARGET_ARCH
    req_target_platform = env.get('TARGET_ARCH')
    debug('vc.py:get_host_target() req_target_platform:%s'%req_target_platform)

    if  req_target_platform:
        # If user requested a specific platform then only try that one.
        target_platform = req_target_platform
    else:
        target_platform = host_platform
        
    try:
        host = _ARCH_TO_CANONICAL[host_platform.lower()]
    except KeyError, e:
        msg = "Unrecognized host architecture %s"
        raise ValueError(msg % repr(host_platform))

    try:
        target = _ARCH_TO_CANONICAL[target_platform.lower()]
    except KeyError, e:
        all_archs = str(_ARCH_TO_CANONICAL.keys())
        raise ValueError("Unrecognized target architecture %s\n\tValid architectures: %s" % (target_platform, all_archs))

    return (host, target,req_target_platform)

# If you update this, update SupportedVSList in Tool/MSCommon/vs.py, and the
# MSVC_VERSION documentation in Tool/msvc.xml.
_VCVER = ["14.0", "14.0Exp", "12.0", "12.0Exp", "11.0", "11.0Exp", "10.0", "10.0Exp", "9.0", "9.0Exp","8.0", "8.0Exp","7.1", "7.0", "6.0"]

_VCVER_TO_PRODUCT_DIR = {
    '14.0' : [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\14.0\Setup\VC\ProductDir')],
    '14.0Exp' : [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\14.0\Setup\VC\ProductDir')],
    '12.0' : [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\12.0\Setup\VC\ProductDir'),
        ],
    '12.0Exp' : [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\12.0\Setup\VC\ProductDir'),
        ],
    '11.0': [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\11.0\Setup\VC\ProductDir'),
        ],
    '11.0Exp' : [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\11.0\Setup\VC\ProductDir'),
        ],
    '10.0': [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\10.0\Setup\VC\ProductDir'),
        ],
    '10.0Exp' : [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\10.0\Setup\VC\ProductDir'),
        ],
    '9.0': [
        (SCons.Util.HKEY_CURRENT_USER, r'Microsoft\DevDiv\VCForPython\9.0\installdir',),
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\9.0\Setup\VC\ProductDir',),
        ],
    '9.0Exp' : [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\9.0\Setup\VC\ProductDir'),
        ],
    '8.0': [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\8.0\Setup\VC\ProductDir'),
        ],
    '8.0Exp': [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\8.0\Setup\VC\ProductDir'),
        ],
    '7.1': [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\7.1\Setup\VC\ProductDir'),
        ],
    '7.0': [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\7.0\Setup\VC\ProductDir'),
        ],
    '6.0': [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\6.0\Setup\Microsoft Visual C++\ProductDir'),
        ]
}
        
def msvc_version_to_maj_min(msvc_version):
   msvc_version_numeric = ''.join([x for  x in msvc_version if x in string_digits + '.'])

   t = msvc_version_numeric.split(".")
   if not len(t) == 2:
       raise ValueError("Unrecognized version %s (%s)" % (msvc_version,msvc_version_numeric))
   try:
       maj = int(t[0])
       min = int(t[1])
       return maj, min
   except ValueError, e:
       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.
    """
    # We assume that any Visual Studio version supports x86 as a target
    if host_target[1] != "x86":
        maj, min = msvc_version_to_maj_min(msvc_version)
        if maj < 8:
            return False

    return True

def find_vc_pdir(msvc_version):
    """Try to find the product directory for the given
    version.

    Note
    ----
    If for some reason the requested version could not be found, an
    exception which inherits from VisualCException will be raised."""
    root = 'Software\\'
    try:
        hkeys = _VCVER_TO_PRODUCT_DIR[msvc_version]
    except KeyError:
        debug("Unknown version of MSVC: %s" % msvc_version)
        raise UnsupportedVersion("Unknown version %s" % msvc_version)

    for hkroot, key in hkeys:
        try:
            comps = None
            if common.is_win64():
                try:
                    # ordinally at win64, try Wow6432Node first.
                    comps = common.read_reg(root + 'Wow6432Node\\' + key, hkroot)
                except WindowsError, e:
                    # at Microsoft Visual Studio for Python 2.7, value is not in Wow6432Node
                    pass
            if not comps:
                # not Win64, or Microsoft Visual Studio for Python 2.7
                comps = common.read_reg(root + key, hkroot)
        except WindowsError, e:
            debug('find_vc_dir(): no VC registry key %s' % repr(key))
        else:
            debug('find_vc_dir(): found VC in registry: %s' % comps)
            if os.path.exists(comps):
                return comps
            else:
                debug('find_vc_dir(): reg says dir is %s, but it does not exist. (ignoring)'\
                          % comps)
                raise MissingConfiguration("registry dir %s not found on the filesystem" % comps)
    return None

def find_batch_file(env,msvc_version,host_arch,target_arch):
    """
    Find the location of the batch script which should set up the compiler
    for any TARGET_ARCH whose compilers were installed by Visual Studio/VCExpress
    """
    pdir = find_vc_pdir(msvc_version)
    if pdir is None:
        raise NoVersionFound("No version of Visual Studio found")
        
    debug('vc.py: find_batch_file() pdir:%s'%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 + "."])
    vernum = float(msvc_ver_numeric)
    if 7 <= vernum < 8:
        pdir = os.path.join(pdir, os.pardir, "Common7", "Tools")
        batfilename = os.path.join(pdir, "vsvars32.bat")
    elif vernum < 7:
        pdir = os.path.join(pdir, "Bin")
        batfilename = os.path.join(pdir, "vcvars32.bat")
    else: # >= 8
        batfilename = os.path.join(pdir, "vcvarsall.bat")

    if not os.path.exists(batfilename):
        debug("Not found: %s" % batfilename)
        batfilename = None
    
    installed_sdks=get_installed_sdks()
    for _sdk in installed_sdks:
        sdk_bat_file = _sdk.get_sdk_vc_script(host_arch,target_arch)
        if not sdk_bat_file:
            debug("vc.py:find_batch_file() not found:%s"%_sdk)
        else:
            sdk_bat_file_path = os.path.join(pdir,sdk_bat_file)
            if os.path.exists(sdk_bat_file_path): 
                debug('vc.py:find_batch_file() sdk_bat_file_path:%s'%sdk_bat_file_path)
                return (batfilename,sdk_bat_file_path)
    return (batfilename,None)


__INSTALLED_VCS_RUN = None

def cached_get_installed_vcs():
    global __INSTALLED_VCS_RUN

    if __INSTALLED_VCS_RUN is None:
        ret = get_installed_vcs()
        __INSTALLED_VCS_RUN = ret

    return __INSTALLED_VCS_RUN

def get_installed_vcs():
    installed_versions = []
    for ver in _VCVER:
        debug('trying to find VC %s' % ver)
        try:
            if find_vc_pdir(ver):
                debug('found VC %s' % ver)
                installed_versions.append(ver)
            else:
                debug('find_vc_pdir return None for ver %s' % ver)
        except VisualCException, e:
            debug('did not find VC %s: caught exception %s' % (ver, str(e)))
    return installed_versions

def reset_installed_vcs():
    """Make it try again to find VC.  This is just for the tests."""
    __INSTALLED_VCS_RUN = None

# Running these batch files isn't cheap: most of the time spent in
# msvs.generate() is due to vcvars*.bat.  In a build that uses "tools='msvs'"
# in multiple environments, for example:
#    env1 = Environment(tools='msvs')
#    env2 = Environment(tools='msvs')
# we can greatly improve the speed of the second and subsequent Environment
# (or Clone) calls by memoizing the environment variables set by vcvars*.bat.
script_env_stdout_cache = {}
def script_env(script, args=None):
    cache_key = (script, args)
    stdout = script_env_stdout_cache.get(cache_key, None)
    if stdout is None:
        stdout = common.get_output(script, args)
        script_env_stdout_cache[cache_key] = stdout

    # Stupid batch files do not set return code: we take a look at the
    # beginning of the output for an error message instead
    olines = stdout.splitlines()
    if olines[0].startswith("The specified configuration type is missing"):
        raise BatchFileExecutionError("\n".join(olines[:2]))

    return common.parse_output(stdout)

def get_default_version(env):
    debug('get_default_version()')

    msvc_version = env.get('MSVC_VERSION')
    msvs_version = env.get('MSVS_VERSION')
    
    debug('get_default_version(): msvc_version:%s msvs_version:%s'%(msvc_version,msvs_version))

    if msvs_version and not msvc_version:
        SCons.Warnings.warn(
                SCons.Warnings.DeprecatedWarning,
                "MSVS_VERSION is deprecated: please use MSVC_VERSION instead ")
        return msvs_version
    elif msvc_version and msvs_version:
        if not msvc_version == msvs_version:
            SCons.Warnings.warn(
                    SCons.Warnings.VisualVersionMismatch,
                    "Requested msvc version (%s) and msvs version (%s) do " \
                    "not match: please use MSVC_VERSION only to request a " \
                    "visual studio version, MSVS_VERSION is deprecated" \
                    % (msvc_version, msvs_version))
        return msvs_version
    if not msvc_version:
        installed_vcs = cached_get_installed_vcs()
        debug('installed_vcs:%s' % installed_vcs)
        if not installed_vcs:
            #msg = 'No installed VCs'
            #debug('msv %s\n' % repr(msg))
            #SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, msg)
            debug('msvc_setup_env: No installed VCs')
            return None
        msvc_version = installed_vcs[0]
        debug('msvc_setup_env: using default installed MSVC version %s\n' % repr(msvc_version))

    return msvc_version

def msvc_setup_env_once(env):
    try:
        has_run  = env["MSVC_SETUP_RUN"]
    except KeyError:
        has_run = False

    if not has_run:
        msvc_setup_env(env)
        env["MSVC_SETUP_RUN"] = True

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)

    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'):
        try_target_archs.append('x86_amd64')
    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
        # The TARGET_ARCH is amd64 then also try 32 bits if there are no viable
        # 64 bit tools installed
        try_target_archs.append('x86')

    debug("msvs_find_valid_batch_script(): host_platform: %s try_target_archs:%s"%(host_platform, try_target_archs))

    d = None
    for tp in try_target_archs:
        # Set to current arch.
        env['TARGET_ARCH']=tp
        
        debug("vc.py:msvc_find_valid_batch_script() trying target_platform:%s"%tp)
        host_target = (host_platform, tp)
        if not is_host_target_supported(host_target, version):
            warn_msg = "host, target = %s not supported for MSVC version %s" % \
                (host_target, version)
            SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg)
        arg = _HOST_TARGET_ARCH_TO_BAT_ARCH[host_target]
        
        # Try to locate a batch file for this host/target platform combo
        try:
            (vc_script,sdk_script) = find_batch_file(env,version,host_platform,tp)
            debug('vc.py:msvc_find_valid_batch_script() vc_script:%s sdk_script:%s'%(vc_script,sdk_script))
        except VisualCException, e:
            msg = str(e)
            debug('Caught exception while looking for batch file (%s)' % msg)
            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())
            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))
        if vc_script:
            try:
                d = script_env(vc_script, args=arg)
            except BatchFileExecutionError, 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
                continue
        if not vc_script and sdk_script:
            debug('vc.py:msvc_find_valid_batch_script() use_script 4: trying sdk script: %s'%(sdk_script))
            try:
                d = script_env(sdk_script)
            except BatchFileExecutionError,e:
                debug('vc.py:msvc_find_valid_batch_script() use_script 5: failed running SDK script %s: Error:%s'%(repr(sdk_script),e))
                continue
        elif not vc_script and not sdk_script:
            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))
        break # We've found a working target_platform, so stop looking
    
    # If we cannot find a viable installed compiler, reset the TARGET_ARCH
    # To it's initial value
    if not d:
        env['TARGET_ARCH']=req_target_platform
    
    return d
    

def msvc_setup_env(env):
    debug('msvc_setup_env()')

    version = get_default_version(env)
    if version is None:
        warn_msg = "No version of Visual Studio compiler found - C/C++ " \
                   "compilers most likely not set correctly"
        SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg)
        return None
    debug('msvc_setup_env: using specified MSVC version %s\n' % repr(version))

    # XXX: we set-up both MSVS version for backward
    # compatibility with the msvs tool
    env['MSVC_VERSION'] = version
    env['MSVS_VERSION'] = version
    env['MSVS'] = {}

    
    use_script = env.get('MSVC_USE_SCRIPT', True)
    if SCons.Util.is_String(use_script):
        debug('vc.py:msvc_setup_env() use_script 1 %s\n' % repr(use_script))
        d = script_env(use_script)
    elif use_script:      
        d = msvc_find_valid_batch_script(env,version)
        debug('vc.py:msvc_setup_env() use_script 2 %s\n' % d)
        if not d:
            return d
    else:
        debug('MSVC_USE_SCRIPT set to False')
        warn_msg = "MSVC_USE_SCRIPT set to False, assuming environment " \
                   "set correctly."
        SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg)
        return None

    for k, v in d.items():
        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()
    if version is None:
        return len(vcs) > 0
    return version in vcs