Skip to content

Commit fa5f5f3

Browse files
authored
Add support for Visual Studio 2022 (#1177)
* Add support for Visual Studio 2022 and migrate to using cmake --build when building on Windows. Leverage the VS2019 MSBuild 'Multi-ToolTask' feature CL_MPCount to saturate project builds properly to 100% CPU usage so building LLVM builds different cpp files in parallel. Clean up some code duplication around Visual Studio support. * flake * Work around Linux bot not having 'cmake --build . -j' flag.
1 parent 974d5c0 commit fa5f5f3

File tree

1 file changed

+62
-171
lines changed

1 file changed

+62
-171
lines changed

emsdk.py

Lines changed: 62 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -284,32 +284,34 @@ def vs_filewhere(installation_path, platform, file):
284284
# Detect which CMake generator to use when building on Windows
285285
if '--mingw' in sys.argv:
286286
CMAKE_GENERATOR = 'MinGW Makefiles'
287+
elif '--vs2022' in sys.argv:
288+
CMAKE_GENERATOR = 'Visual Studio 17'
289+
elif '--vs2019' in sys.argv:
290+
CMAKE_GENERATOR = 'Visual Studio 16'
287291
elif '--vs2017' in sys.argv:
288292
CMAKE_GENERATOR = 'Visual Studio 15'
289-
elif '--vs2019' in sys.argv:
293+
elif len(vswhere(17)) > 0:
294+
CMAKE_GENERATOR = 'Visual Studio 17'
295+
elif len(vswhere(16)) > 0:
290296
CMAKE_GENERATOR = 'Visual Studio 16'
297+
elif len(vswhere(15)) > 0:
298+
# VS2017 has an LLVM build issue, see
299+
# https://github.com/kripken/emscripten-fastcomp/issues/185
300+
CMAKE_GENERATOR = 'Visual Studio 15'
301+
elif which('mingw32-make') is not None and which('g++') is not None:
302+
CMAKE_GENERATOR = 'MinGW Makefiles'
291303
else:
292-
vs2019_exists = len(vswhere(16)) > 0
293-
vs2017_exists = len(vswhere(15)) > 0
294-
mingw_exists = which('mingw32-make') is not None and which('g++') is not None
295-
if vs2019_exists:
296-
CMAKE_GENERATOR = 'Visual Studio 16'
297-
elif vs2017_exists:
298-
# VS2017 has an LLVM build issue, see
299-
# https://github.com/kripken/emscripten-fastcomp/issues/185
300-
CMAKE_GENERATOR = 'Visual Studio 15'
301-
elif mingw_exists:
302-
CMAKE_GENERATOR = 'MinGW Makefiles'
303-
else:
304-
# No detected generator
305-
CMAKE_GENERATOR = ''
304+
# No detected generator
305+
CMAKE_GENERATOR = ''
306306

307307

308-
sys.argv = [a for a in sys.argv if a not in ('--mingw', '--vs2017', '--vs2019')]
308+
sys.argv = [a for a in sys.argv if a not in ('--mingw', '--vs2017', '--vs2019', '--vs2022')]
309309

310310

311311
# Computes a suitable path prefix to use when building with a given generator.
312312
def cmake_generator_prefix():
313+
if CMAKE_GENERATOR == 'Visual Studio 17':
314+
return '_vs2022'
313315
if CMAKE_GENERATOR == 'Visual Studio 16':
314316
return '_vs2019'
315317
if CMAKE_GENERATOR == 'Visual Studio 15':
@@ -861,14 +863,7 @@ def decide_cmake_build_type(tool):
861863

862864
# The root directory of the build.
863865
def llvm_build_dir(tool):
864-
generator_suffix = ''
865-
if CMAKE_GENERATOR == 'Visual Studio 15':
866-
generator_suffix = '_vs2017'
867-
elif CMAKE_GENERATOR == 'Visual Studio 16':
868-
generator_suffix = '_vs2019'
869-
elif CMAKE_GENERATOR == 'MinGW Makefiles':
870-
generator_suffix = '_mingw'
871-
866+
generator_suffix = cmake_generator_prefix()
872867
bitness_suffix = '_32' if tool.bitness == 32 else '_64'
873868

874869
if hasattr(tool, 'git_branch'):
@@ -916,112 +911,38 @@ def build_env(generator):
916911
# See https://groups.google.com/forum/#!topic/emscripten-discuss/5Or6QIzkqf0
917912
if MACOS:
918913
build_env['CXXFLAGS'] = ((build_env['CXXFLAGS'] + ' ') if hasattr(build_env, 'CXXFLAGS') else '') + '-stdlib=libc++'
919-
elif 'Visual Studio 15' in generator or 'Visual Studio 16' in generator:
920-
if 'Visual Studio 16' in generator:
921-
path = vswhere(16)
922-
else:
923-
path = vswhere(15)
924-
925-
# Configuring CMake for Visual Studio needs and env. var VCTargetsPath to be present.
926-
# How this is supposed to work is unfortunately very undocumented. See
927-
# https://discourse.cmake.org/t/cmake-failed-to-get-the-value-of-vctargetspath-with-vs2019-16-7/1839/16
928-
# for some conversation. Try a couple of common paths if one of them would work.
929-
# In the future as new versions of VS come out, we likely need to add new paths into this list.
930-
if 'VCTargetsPath' not in build_env:
931-
vctargets_paths = [
932-
os.path.join(path, 'MSBuild\\Microsoft\\VC\\v160\\'),
933-
os.path.join(path, 'Common7\\IDE\\VC\\VCTargets')
934-
]
935-
for p in vctargets_paths:
936-
if os.path.isfile(os.path.join(p, 'Microsoft.Cpp.Default.props')):
937-
debug_print('Set env. var VCTargetsPath=' + p + ' for CMake.')
938-
build_env['VCTargetsPath'] = p
939-
break
940-
else:
941-
debug_print('Searched path ' + p + ' as candidate for VCTargetsPath, not working.')
942-
943-
if 'VCTargetsPath' not in build_env:
944-
errlog('Unable to locate Visual Studio compiler installation for generator "' + generator + '"!')
945-
errlog('Either rerun installation in Visual Studio Command Prompt, or locate directory to Microsoft.Cpp.Default.props manually')
946-
sys.exit(1)
947-
948-
# CMake and VS2017 cl.exe needs to have mspdb140.dll et al. in its PATH.
949-
vc_bin_paths = [vs_filewhere(path, 'amd64', 'cl.exe'),
950-
vs_filewhere(path, 'x86', 'cl.exe')]
951-
for path in vc_bin_paths:
952-
if os.path.isdir(path):
953-
build_env['PATH'] = build_env['PATH'] + ';' + path
954-
914+
if WINDOWS:
915+
# MSBuild.exe has an internal mechanism to avoid N^2 oversubscription of threads in its two-tier build model, see
916+
# https://devblogs.microsoft.com/cppblog/improved-parallelism-in-msbuild/
917+
build_env['UseMultiToolTask'] = 'true'
918+
build_env['EnforceProcessCountAcrossBuilds'] = 'true'
955919
return build_env
956920

957921

958-
def get_generator_for_sln_file(sln_file):
959-
contents = open(sln_file, 'r').read()
960-
if '# Visual Studio 16' in contents or '# Visual Studio Version 16' in contents: # VS2019
961-
return 'Visual Studio 16'
962-
if '# Visual Studio 15' in contents: # VS2017
963-
return 'Visual Studio 15'
964-
raise Exception('Unknown generator used to build solution file ' + sln_file)
965-
966-
967-
def find_msbuild(sln_file):
968-
# The following logic attempts to find a Visual Studio version specific
969-
# MSBuild.exe from a list of known locations.
970-
generator = get_generator_for_sln_file(sln_file)
971-
debug_print('find_msbuild looking for generator ' + str(generator))
972-
if generator == 'Visual Studio 16': # VS2019
973-
path = vswhere(16)
974-
search_paths = [os.path.join(path, 'MSBuild/Current/Bin'),
975-
os.path.join(path, 'MSBuild/15.0/Bin/amd64'),
976-
os.path.join(path, 'MSBuild/15.0/Bin')]
977-
elif generator == 'Visual Studio 15': # VS2017
978-
path = vswhere(15)
979-
search_paths = [os.path.join(path, 'MSBuild/15.0/Bin/amd64'),
980-
os.path.join(path, 'MSBuild/15.0/Bin')]
981-
else:
982-
raise Exception('Unknown generator!')
983-
984-
for path in search_paths:
985-
p = os.path.join(path, 'MSBuild.exe')
986-
debug_print('Searching for MSBuild.exe: ' + p)
987-
if os.path.isfile(p):
988-
return p
989-
debug_print('MSBuild.exe in PATH? ' + str(which('MSBuild.exe')))
990-
# Last fallback, try any MSBuild from PATH (might not be compatible, but best effort)
991-
return which('MSBuild.exe')
992-
993-
994922
def make_build(build_root, build_type, build_target_platform='x64'):
995923
debug_print('make_build(build_root=' + build_root + ', build_type=' + build_type + ', build_target_platform=' + build_target_platform + ')')
996924
if CPU_CORES > 1:
997925
print('Performing a parallel build with ' + str(CPU_CORES) + ' cores.')
998926
else:
999927
print('Performing a singlethreaded build.')
1000928

1001-
generator_to_use = CMAKE_GENERATOR
1002-
1003-
if WINDOWS:
1004-
if 'Visual Studio' in CMAKE_GENERATOR:
1005-
solution_name = str(subprocess.check_output(['dir', '/b', '*.sln'], shell=True, cwd=build_root).decode('utf-8').strip())
1006-
generator_to_use = get_generator_for_sln_file(os.path.join(build_root, solution_name))
1007-
# Disabled for now: Don't pass /maxcpucount argument to msbuild, since it
1008-
# looks like when building, msbuild already automatically spawns the full
1009-
# amount of logical cores the system has, and passing the number of
1010-
# logical cores here has been observed to give a quadratic N*N explosion
1011-
# on the number of spawned processes (e.g. on a Core i7 5960X with 16
1012-
# logical cores, it would spawn 16*16=256 cl.exe processes, which would
1013-
# start crashing when running out of system memory)
1014-
# make = [find_msbuild(os.path.join(build_root, solution_name)), '/maxcpucount:' + str(CPU_CORES), '/t:Build', '/p:Configuration=' + build_type, '/nologo', '/verbosity:minimal', solution_name]
1015-
make = [find_msbuild(os.path.join(build_root, solution_name)), '/t:Build', '/p:Configuration=' + build_type, '/p:Platform=' + build_target_platform, '/nologo', '/verbosity:minimal', solution_name]
1016-
else:
1017-
make = ['mingw32-make', '-j' + str(CPU_CORES)]
929+
make = ['cmake', '--build', '.', '--config', build_type]
930+
if 'Visual Studio' in CMAKE_GENERATOR:
931+
# Visual Studio historically has had a two-tier problem in its build system design. A single MSBuild.exe instance only governs
932+
# the build of a single project (.exe/.lib/.dll) in a solution. Passing the -j parameter above will only enable multiple MSBuild.exe
933+
# instances to be spawned to build multiple projects in parallel, but each MSBuild.exe is still singlethreaded.
934+
# To enable each MSBuild.exe instance to also compile several .cpp files in parallel inside a single project, pass the extra
935+
# MSBuild.exe specific "Multi-ToolTask" (MTT) setting /p:CL_MPCount. This enables each MSBuild.exe to parallelize builds wide.
936+
# This requires CMake 3.12 or newer.
937+
make += ['-j', str(CPU_CORES), '--', '/p:CL_MPCount=' + str(CPU_CORES)]
1018938
else:
1019-
make = ['cmake', '--build', '.', '--', '-j' + str(CPU_CORES)]
939+
# Pass -j to native make, CMake might not support -j option.
940+
make += ['--', '-j', str(CPU_CORES)]
1020941

1021942
# Build
1022943
try:
1023944
print('Running build: ' + str(make))
1024-
ret = subprocess.check_call(make, cwd=build_root, env=build_env(generator_to_use))
945+
ret = subprocess.check_call(make, cwd=build_root, env=build_env(CMAKE_GENERATOR))
1025946
if ret != 0:
1026947
errlog('Build failed with exit code ' + ret + '!')
1027948
errlog('Working directory: ' + build_root)
@@ -1108,6 +1029,21 @@ def xcode_sdk_version():
11081029
return subprocess.checkplatform.mac_ver()[0].split('.')
11091030

11101031

1032+
def get_generator_and_config_args(tool):
1033+
args = []
1034+
cmake_generator = CMAKE_GENERATOR
1035+
if 'Visual Studio 16' in CMAKE_GENERATOR or 'Visual Studio 17' in CMAKE_GENERATOR: # VS2019 or VS2022
1036+
# With Visual Studio 16 2019, CMake changed the way they specify target arch.
1037+
# Instead of appending it into the CMake generator line, it is specified
1038+
# with a -A arch parameter.
1039+
args += ['-A', 'x64' if tool.bitness == 64 else 'x86']
1040+
args += ['-Thost=x64']
1041+
elif 'Visual Studio' in CMAKE_GENERATOR and tool.bitness == 64:
1042+
cmake_generator += ' Win64'
1043+
args += ['-Thost=x64']
1044+
return (cmake_generator, args)
1045+
1046+
11111047
def build_llvm(tool):
11121048
debug_print('build_llvm(' + str(tool) + ')')
11131049
llvm_root = tool.installation_path()
@@ -1150,16 +1086,7 @@ def build_llvm(tool):
11501086
# (there instead of $(Configuration), one would need ${CMAKE_BUILD_TYPE} ?)
11511087
# It looks like compiler-rt is not compatible to build on Windows?
11521088
args += ['-DLLVM_ENABLE_PROJECTS=clang;lld']
1153-
cmake_generator = CMAKE_GENERATOR
1154-
if 'Visual Studio 16' in CMAKE_GENERATOR: # VS2019
1155-
# With Visual Studio 16 2019, CMake changed the way they specify target arch.
1156-
# Instead of appending it into the CMake generator line, it is specified
1157-
# with a -A arch parameter.
1158-
args += ['-A', 'x64' if tool.bitness == 64 else 'x86']
1159-
args += ['-Thost=x64']
1160-
elif 'Visual Studio' in CMAKE_GENERATOR and tool.bitness == 64:
1161-
cmake_generator += ' Win64'
1162-
args += ['-Thost=x64']
1089+
cmake_generator, args = get_generator_and_config_args(tool)
11631090

11641091
if os.getenv('LLVM_CMAKE_ARGS'):
11651092
extra_args = os.environ['LLVM_CMAKE_ARGS'].split(',')
@@ -1190,17 +1117,7 @@ def build_ninja(tool):
11901117
build_type = decide_cmake_build_type(tool)
11911118

11921119
# Configure
1193-
cmake_generator = CMAKE_GENERATOR
1194-
args = []
1195-
if 'Visual Studio 16' in CMAKE_GENERATOR: # VS2019
1196-
# With Visual Studio 16 2019, CMake changed the way they specify target arch.
1197-
# Instead of appending it into the CMake generator line, it is specified
1198-
# with a -A arch parameter.
1199-
args += ['-A', 'x64' if tool.bitness == 64 else 'x86']
1200-
args += ['-Thost=x64']
1201-
elif 'Visual Studio' in CMAKE_GENERATOR and tool.bitness == 64:
1202-
cmake_generator += ' Win64'
1203-
args += ['-Thost=x64']
1120+
cmake_generator, args = get_generator_and_config_args(tool)
12041121

12051122
cmakelists_dir = os.path.join(src_root)
12061123
success = cmake_configure(cmake_generator, build_root, cmakelists_dir, build_type, args)
@@ -1239,17 +1156,8 @@ def build_ccache(tool):
12391156
build_type = decide_cmake_build_type(tool)
12401157

12411158
# Configure
1242-
cmake_generator = CMAKE_GENERATOR
1243-
args = ['-DZSTD_FROM_INTERNET=ON']
1244-
if 'Visual Studio 16' in CMAKE_GENERATOR: # VS2019
1245-
# With Visual Studio 16 2019, CMake changed the way they specify target arch.
1246-
# Instead of appending it into the CMake generator line, it is specified
1247-
# with a -A arch parameter.
1248-
args += ['-A', 'x64' if tool.bitness == 64 else 'x86']
1249-
args += ['-Thost=x64']
1250-
elif 'Visual Studio' in CMAKE_GENERATOR and tool.bitness == 64:
1251-
cmake_generator += ' Win64'
1252-
args += ['-Thost=x64']
1159+
cmake_generator, args = get_generator_and_config_args(tool)
1160+
args += ['-DZSTD_FROM_INTERNET=ON']
12531161

12541162
cmakelists_dir = os.path.join(src_root)
12551163
success = cmake_configure(cmake_generator, build_root, cmakelists_dir, build_type, args)
@@ -1409,17 +1317,8 @@ def emscripten_post_install(tool):
14091317
build_root = optimizer_build_root(tool)
14101318
build_type = decide_cmake_build_type(tool)
14111319

1412-
args = []
1413-
14141320
# Configure
1415-
cmake_generator = CMAKE_GENERATOR
1416-
if 'Visual Studio 16' in CMAKE_GENERATOR: # VS2019
1417-
# With Visual Studio 16 2019, CMake changed the way they specify target arch.
1418-
# Instead of appending it into the CMake generator line, it is specified
1419-
# with a -A arch parameter.
1420-
args += ['-A', 'x64' if tool.bitness == 64 else 'x86']
1421-
elif 'Visual Studio' in CMAKE_GENERATOR and tool.bitness == 64:
1422-
cmake_generator += ' Win64'
1321+
cmake_generator, args = get_generator_and_config_args(tool)
14231322

14241323
success = cmake_configure(cmake_generator, build_root, src_root, build_type, args)
14251324
if not success:
@@ -1464,16 +1363,8 @@ def build_binaryen_tool(tool):
14641363
build_type = decide_cmake_build_type(tool)
14651364

14661365
# Configure
1467-
args = ['-DENABLE_WERROR=0'] # -Werror is not useful for end users
1468-
1469-
cmake_generator = CMAKE_GENERATOR
1470-
if 'Visual Studio 16' in CMAKE_GENERATOR: # VS2019
1471-
# With Visual Studio 16 2019, CMake changed the way they specify target arch.
1472-
# Instead of appending it into the CMake generator line, it is specified
1473-
# with a -A arch parameter.
1474-
args += ['-A', 'x64' if tool.bitness == 64 else 'x86']
1475-
elif 'Visual Studio' in CMAKE_GENERATOR and tool.bitness == 64:
1476-
cmake_generator += ' Win64'
1366+
cmake_generator, args = get_generator_and_config_args(tool)
1367+
args += ['-DENABLE_WERROR=0'] # -Werror is not useful for end users
14771368

14781369
if 'Visual Studio' in CMAKE_GENERATOR:
14791370
if BUILD_FOR_TESTING:
@@ -2794,7 +2685,7 @@ def main(args):
27942685
purposes. Default: Enabled
27952686
--disable-assertions: Forces assertions off during the build.
27962687
2797-
--vs2017/--vs2019: If building from source, overrides to build
2688+
--vs2017/--vs2019/--vs2022: If building from source, overrides to build
27982689
using the specified compiler. When installing
27992690
precompiled packages, this has no effect.
28002691
Note: The same compiler specifier must be
@@ -2817,7 +2708,7 @@ def main(args):
28172708

28182709
if WINDOWS:
28192710
print('''
2820-
emsdk activate [--permanent] [--system] [--build=type] [--vs2017/--vs2019] <tool/sdk>
2711+
emsdk activate [--permanent] [--system] [--build=type] [--vs2017/--vs2019/--vs2022] <tool/sdk>
28212712
28222713
- Activates the given tool or SDK in the
28232714
environment of the current shell.
@@ -2831,8 +2722,8 @@ def main(args):
28312722
(uses Machine environment variables).
28322723
28332724
- If a custom compiler version was used to override
2834-
the compiler to use, pass the same --vs2017/--vs2019 parameter
2835-
here to choose which version to activate.
2725+
the compiler to use, pass the same --vs2017/--vs2019/--vs2022
2726+
parameter here to choose which version to activate.
28362727
28372728
emcmdprompt.bat - Spawns a new command prompt window with the
28382729
Emscripten environment active.''')

0 commit comments

Comments
 (0)