gecko-dev/build/moz.configure/windows.configure

501 lines
18 KiB
Python

# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
option('--with-windows-version', nargs=1, default='603',
help='Windows SDK version to target. Win 8.1 (603) is currently'
'the minimum supported version.')
@depends('--with-windows-version')
@imports(_from='__builtin__', _import='ValueError')
def valid_windows_version(value):
if not value:
die('Cannot build with --without-windows-version')
try:
version = int(value[0], 16)
if version in (0x603,):
return version
except ValueError:
pass
die('Invalid value for --with-windows-version (%s)', value[0])
option(env='WINDOWSSDKDIR', nargs=1,
help='Directory containing the Windows SDK')
@depends('WINDOWSSDKDIR', host)
def windows_sdk_dir(value, host):
if value:
return value
if host.kernel != 'WINNT':
return ()
return set(x[1] for x in get_registry_values(
r'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Kits\Installed Roots'
r'\KitsRoot*', get_32_and_64_bit=True))
# The Windows SDK 8.1 and 10 have different layouts. The former has
# $SDK/include/$subdir, while the latter has $SDK/include/$version/$subdir.
# The vcvars* scripts don't actually care about the version, they just take
# the last alphanumerically.
# The $SDK/lib directories always have version subdirectories, but while the
# versions match the one in $SDK/include for SDK 10, it's "winv6.3" for SDK
# 8.1.
@imports('os')
@imports('re')
@imports(_from='__builtin__', _import='sorted')
@imports(_from='__builtin__', _import='WindowsError')
def get_sdk_dirs(sdk, subdir):
def get_dirs_containing(sdk, stem, subdir):
base = os.path.join(sdk, stem)
try:
subdirs = [d for d in os.listdir(base)
if os.path.isdir(os.path.join(base, d))]
except WindowsError:
subdirs = []
if not subdirs:
return ()
if subdir in subdirs:
return (base,)
# At this point, either we have an incomplete or invalid SDK directory,
# or we exclusively have version numbers in subdirs.
return tuple(os.path.join(base, s) for s in subdirs
if os.path.isdir(os.path.join(base, s, subdir)))
def categorize(dirs):
return {os.path.basename(d): d for d in dirs}
include_dirs = categorize(get_dirs_containing(sdk, 'include', subdir))
lib_dirs = categorize(get_dirs_containing(sdk, 'lib', subdir))
if 'include' in include_dirs:
include_dirs['winv6.3'] = include_dirs['include']
del include_dirs['include']
valid_versions = sorted(set(include_dirs) & set(lib_dirs), reverse=True)
if valid_versions:
return namespace(
path=sdk,
lib=lib_dirs[valid_versions[0]],
include=include_dirs[valid_versions[0]],
)
@imports(_from='mozbuild.shellutil', _import='quote')
def valid_windows_sdk_dir_result(value):
if value:
return '0x%04x in %s' % (value.version, quote(value.path))
@depends(c_compiler, windows_sdk_dir, valid_windows_version, 'WINDOWSSDKDIR')
@checking('for Windows SDK', valid_windows_sdk_dir_result)
@imports(_from='__builtin__', _import='sorted')
@imports(_from='__builtin__', _import='Exception')
@imports(_from='textwrap', _import='dedent')
def valid_windows_sdk_dir(compiler, windows_sdk_dir, target_version,
windows_sdk_dir_env):
if windows_sdk_dir_env:
windows_sdk_dir_env = windows_sdk_dir_env[0]
sdks = {}
for d in windows_sdk_dir:
sdk = get_sdk_dirs(d, 'um')
if sdk:
check = dedent('''\
#include <winsdkver.h>
WINVER_MAXVER
''')
um_dir = os.path.join(sdk.include, 'um')
shared_dir = os.path.join(sdk.include, 'shared')
result = try_preprocess(compiler.wrapper + [compiler.compiler] +
compiler.flags +
['-I', um_dir, '-I', shared_dir], 'C',
check)
if result:
maxver = result.splitlines()[-1]
try:
maxver = int(maxver, 0)
except Exception:
pass
else:
sdks[d] = maxver, sdk
continue
if d == windows_sdk_dir_env:
raise FatalCheckError(
'Error while checking the version of the SDK in '
'WINDOWSSDKDIR (%s). Please verify it contains a valid and '
'complete SDK installation.' % windows_sdk_dir_env)
valid_sdks = sorted(sdks, key=lambda x: sdks[x][0], reverse=True)
if valid_sdks:
biggest_version, sdk = sdks[valid_sdks[0]]
if not valid_sdks or biggest_version < target_version:
if windows_sdk_dir_env:
raise FatalCheckError(
'You are targeting Windows version 0x%04x, but your SDK only '
'supports up to version 0x%04x. Install and use an updated SDK, '
'or target a lower version using --with-windows-version. '
'Alternatively, try running the Windows SDK Configuration Tool '
'and selecting a newer SDK. See '
'https://developer.mozilla.org/En/Windows_SDK_versions for '
'details on fixing this.' % (target_version, biggest_version))
raise FatalCheckError(
'Cannot find a Windows SDK for version >= 0x%04x.' % target_version)
return namespace(
path=sdk.path,
include=sdk.include,
lib=sdk.lib,
version=biggest_version,
)
@imports(_from='mozbuild.shellutil', _import='quote')
def valid_ucrt_sdk_dir_result(value):
if value:
return '%s in %s' % (value.version, quote(value.path))
@depends(windows_sdk_dir, 'WINDOWSSDKDIR', c_compiler)
@checking('for Universal CRT SDK', valid_ucrt_sdk_dir_result)
@imports('os')
@imports(_from='__builtin__', _import='sorted')
@imports(_import='mozpack.path', _as='mozpath')
def valid_ucrt_sdk_dir(windows_sdk_dir, windows_sdk_dir_env, c_compiler):
if windows_sdk_dir_env:
windows_sdk_dir_env = windows_sdk_dir_env[0]
sdks = {}
for d in windows_sdk_dir:
sdk = get_sdk_dirs(d, 'ucrt')
if sdk:
version = os.path.basename(sdk.include)
# We're supposed to always find a version in the directory, because
# the 8.1 SDK, which doesn't have a version in the directory, doesn't
# contain the Universal CRT SDK. When the main SDK is 8.1, there
# is, however, supposed to be a reduced install of the SDK 10
# with the UCRT.
if version != 'include':
sdks[d] = Version(version), sdk
continue
if d == windows_sdk_dir_env:
# When WINDOWSSDKDIR is set in the environment and we can't find the
# Universal CRT SDK, chances are this is a start-shell-msvc*.bat
# setup, where INCLUDE and LIB already contain the UCRT paths.
ucrt_includes = [
p for p in os.environ.get('INCLUDE', '').split(os.pathsep)
if os.path.basename(p).lower() == 'ucrt'
]
ucrt_libs = [
p for p in os.environ.get('LIB', '').split(os.pathsep)
if os.path.basename(os.path.dirname(p)).lower() == 'ucrt'
]
if ucrt_includes and ucrt_libs:
# Pick the first of each, since they are the ones that the
# compiler would look first. Assume they contain the SDK files.
include = os.path.dirname(ucrt_includes[0])
lib = os.path.dirname(os.path.dirname(ucrt_libs[0]))
path = os.path.dirname(os.path.dirname(include))
version = os.path.basename(include)
if version != 'include' and mozpath.basedir(lib, [path]):
sdks[d] = Version(version), namespace(
path=path,
include=include,
lib=lib,
)
continue
raise FatalCheckError(
'The SDK in WINDOWSSDKDIR (%s) does not contain the Universal '
'CRT.' % windows_sdk_dir_env)
valid_sdks = sorted(sdks, key=lambda x: sdks[x][0], reverse=True)
if not valid_sdks:
raise FatalCheckError('Cannot find the Universal CRT SDK. '
'Please install it.')
version, sdk = sdks[valid_sdks[0]]
minimum_ucrt_version = Version('10.0.15063.0')
if version < minimum_ucrt_version:
raise FatalCheckError('Latest Universal CRT SDK version found %s'
' and minimum required is %s. This or a later'
' version can be installed using the Visual'
' Studio installer.'
% (version, minimum_ucrt_version))
broken_ucrt_version = Version('10.0.16299.0')
working_ucrt_version = Version('10.0.17134.0')
if (c_compiler.type == 'clang-cl' and version >= broken_ucrt_version and
version < working_ucrt_version):
raise FatalCheckError('Found SDK version %s but clang-cl builds'
' currently don\'t work with the SDK version.'
' You should use a different version, either'
' by uninstalling version %s or setting a'
' custom WINDOWSSDKDIR.\n'
'Note: Version %s now works with clang-cl.'
% (version, version, working_ucrt_version))
return namespace(
path=sdk.path,
include=sdk.include,
lib=sdk.lib,
version=version,
)
@depends(c_compiler, toolchain_search_path)
@imports('os')
def vc_path(c_compiler, toolchain_search_path):
if c_compiler.type != 'clang-cl':
return
# In clang-cl builds, we need the headers and libraries from an MSVC installation.
vc_program = find_program('cl.exe', paths=toolchain_search_path)
if not vc_program:
die('Cannot find a Visual C++ install for e.g. ATL headers.')
result = os.path.dirname(vc_program)
while True:
next, p = os.path.split(result)
if next == result:
die('Cannot determine the Visual C++ directory the compiler (%s) '
'is in' % vc_program)
result = next
if p.lower() == 'bin':
break
return os.path.normpath(result)
option(env='DIA_SDK_PATH', nargs=1,
help='Path to the Debug Interface Access SDK')
@depends(vc_path, 'DIA_SDK_PATH')
@checking('for the Debug Interface Access SDK', lambda x: x or 'not found')
@imports('os')
def dia_sdk_dir(vc_path, dia_sdk_path):
if dia_sdk_path:
path = os.path.normpath(dia_sdk_path[0])
elif vc_path:
# This would be easier if we had the installationPath that
# get_vc_paths works with, since 'DIA SDK' is relative to that.
path = os.path.normpath(os.path.join(
vc_path, r'..\..\..\..\DIA SDK'))
if os.path.exists(os.path.join(path, 'include', 'dia2.h')):
return path
@depends(vc_path, valid_windows_sdk_dir, valid_ucrt_sdk_dir, dia_sdk_dir)
@imports('os')
def include_path(vc_path, windows_sdk_dir, ucrt_sdk_dir, dia_sdk_dir):
if not vc_path:
return
atlmfc_dir = os.path.join(vc_path, 'atlmfc', 'include')
if not os.path.isdir(atlmfc_dir):
die('Cannot find the ATL/MFC headers in the Visual C++ directory (%s). '
'Please install them.' % vc_path)
winrt_dir = os.path.join(windows_sdk_dir.include, 'winrt')
if not os.path.isdir(winrt_dir):
die('Cannot find the WinRT headers in the Windows SDK directory (%s). '
'Please install them.' % windows_sdk_dir.path)
includes = []
include_env = os.environ.get('INCLUDE')
if include_env:
includes.append(include_env)
includes.extend((
os.path.join(vc_path, 'include'),
atlmfc_dir,
os.path.join(windows_sdk_dir.include, 'shared'),
os.path.join(windows_sdk_dir.include, 'um'),
winrt_dir,
os.path.join(ucrt_sdk_dir.include, 'ucrt'),
))
if dia_sdk_dir:
includes.append(os.path.join(dia_sdk_dir, 'include'))
# Set in the environment for old-configure
includes = os.pathsep.join(includes)
os.environ['INCLUDE'] = includes
return includes
set_config('INCLUDE', include_path)
@template
def dia_sdk_subdir(host_or_target, subdir):
@depends(dia_sdk_dir, host_or_target, dependable(subdir))
def dia_sdk_subdir(dia_sdk_dir, target, subdir):
if not dia_sdk_dir:
return
# For some reason the DIA SDK still uses the old-style targets
# even in a newer MSVC.
old_target = {
'x86': '',
'x86_64': 'amd64',
'arm': 'arm',
'aarch64': 'arm64'
}.get(target.cpu)
if old_target is None:
return
# As old_target can be '', and os.path.join will happily use the empty
# string, leading to a string ending with a backslash, that Make will
# interpret as a "string continues on next line" indicator, use variable
# args.
old_target = (old_target,) if old_target else ()
return os.path.join(dia_sdk_dir, subdir, *old_target)
return dia_sdk_subdir
set_config('WIN_DIA_SDK_BIN_DIR', dia_sdk_subdir(host, 'bin'))
@template
def lib_path_for(host_or_target):
@depends(host_or_target, dependable(host_or_target is host), vc_path,
valid_windows_sdk_dir, valid_ucrt_sdk_dir, dia_sdk_subdir(host_or_target, 'lib'))
@imports('os')
def lib_path(target, is_host, vc_path, windows_sdk_dir, ucrt_sdk_dir, dia_sdk_lib_dir):
if not vc_path:
return
sdk_target = {
'x86': 'x86',
'x86_64': 'x64',
'arm': 'arm',
'aarch64': 'arm64',
}.get(target.cpu)
# MSVC2017 switched to use the same target naming as the sdk.
atlmfc_dir = os.path.join(vc_path, 'atlmfc', 'lib', sdk_target)
if not os.path.isdir(atlmfc_dir):
die('Cannot find the ATL/MFC libraries in the Visual C++ directory '
'(%s). Please install them.' % vc_path)
libs = []
lib_env = os.environ.get('LIB')
if lib_env and not is_host:
libs.extend(lib_env.split(os.pathsep))
libs.extend((
os.path.join(vc_path, 'lib', sdk_target),
atlmfc_dir,
os.path.join(windows_sdk_dir.lib, 'um', sdk_target),
os.path.join(ucrt_sdk_dir.lib, 'ucrt', sdk_target),
))
if dia_sdk_lib_dir:
libs.append(dia_sdk_lib_dir)
return libs
return lib_path
@depends(lib_path_for(target))
@imports('os')
def lib_path(libs):
# Set in the environment for old-configure
libs = os.pathsep.join(libs)
os.environ['LIB'] = libs
return libs
set_config('LIB', lib_path)
lib_path_for_host = lib_path_for(host)
@depends(lib_path_for_host)
@imports(_from='mozbuild.shellutil', _import='quote')
def host_linker_libpaths(libs):
return ['-LIBPATH:%s' % quote(l) for l in libs]
@depends(lib_path_for_host)
@imports(_from='mozbuild.shellutil', _import='quote')
def host_linker_libpaths_bat(libs):
# .bat files need a different style of quoting. Batch quoting is actually
# not defined, and up to applications to handle, so it's not really clear
# what should be escaped and what not, but most paths should work just
# fine without escaping. And we don't care about double-quotes possibly
# having to be escaped because they're not allowed in file names on
# Windows.
return ['"-LIBPATH:%s"' % l for l in libs]
set_config('HOST_LINKER_LIBPATHS', host_linker_libpaths)
set_config('HOST_LINKER_LIBPATHS_BAT', host_linker_libpaths_bat)
option(env='MT', nargs=1, help='Path to the Microsoft Manifest Tool')
@depends(valid_windows_sdk_dir, valid_ucrt_sdk_dir)
@imports(_from='os', _import='environ')
@imports('platform')
def sdk_bin_path(valid_windows_sdk_dir, valid_ucrt_sdk_dir):
if not valid_windows_sdk_dir:
return
vc_host = {
'x86': 'x86',
'AMD64': 'x64',
}.get(platform.machine())
# From version 10.0.15063.0 onwards the bin path contains the version number.
versioned_bin = ('bin' if valid_ucrt_sdk_dir.version < '10.0.15063.0'
else os.path.join('bin', str(valid_ucrt_sdk_dir.version)))
result = [
environ['PATH'],
os.path.join(valid_windows_sdk_dir.path, versioned_bin, vc_host)
]
if vc_host == 'x64':
result.append(
os.path.join(valid_windows_sdk_dir.path, versioned_bin, 'x86'))
return result
mt = check_prog('MT', ('mt.exe',), input='MT',
paths=sdk_bin_path)
# Check that MT is not something unexpected like "magnetic tape manipulation
# utility".
@depends(mt)
@checking('whether MT is really Microsoft Manifest Tool', lambda x: bool(x))
@imports('subprocess')
def valid_mt(path):
try:
out = subprocess.check_output([path]).splitlines()
out = '\n'.join(l for l in out
if 'Microsoft (R) Manifest Tool' in l)
if out:
return path
except subprocess.CalledProcessError:
pass
raise FatalCheckError('%s is not Microsoft Manifest Tool')
set_config('MSMANIFEST_TOOL', depends(valid_mt)(lambda x: bool(x)))
js_option(env='LINKER', nargs=1, help='Path to the linker')
link = check_prog('LINKER', ('lld-link',), input='LINKER',
paths=toolchain_search_path)
js_option(env='HOST_LINKER', nargs=1, help='Path to the host linker')
host_link = check_prog('HOST_LINKER', ('lld-link',), input='HOST_LINKER',
paths=host_toolchain_search_path)
add_old_configure_assignment('LINKER', link)
check_prog('MAKECAB', ('makecab.exe',))