Thanks for addressing this! I'm not on the VS CMake team anymore so I probably won't get around to testing out your fix, but it sounds like it could work. I'll let the old team know in case they're still tracking this.
As for why you're not able to repro, did you try on the command line? If I remember correctly, it only reproduced outside of Visual Studio. It could be a VS 2019 vs 2022 thing as well though.
vswhere.exe will report the full version (installationVersion
is the property name).
I'm not aware of any official documentation but the description provided is accurate to my understanding. Craig mentioned it in the issue but BBB
won't always be 3 digits.
Also related to this is that when configuring from VS (so that CMAKE_CONFIGURATION_TYPES is respected) configuring fails with an error message like:
C:\Program Files\Microsoft Visual Studio\2022\Preview\MSBuild\Microsoft\VC\v170\Microsoft.CppBuild.targets(437,5): error MSB8013: This project doesn't contain the Configuration and Platform combination of Debug|x64. [\_deps\eigen-subbuild\ZERO_CHECK.vcxproj]
(this is using Visual Studio 17 2022 generator and CMAKE_CONFIGURATION_TYPES=Release, it doesn't repro with Ninja Multi-Config)
What is a subbuild (I didn't find any details in the docs)? I'm assuming the error is because MSBuild is defaulting to Debug configuration, which suggests CMAKE_CONFIGURATION_TYPES isn't getting handed off to the subbuild correctly.
When configuring with a multi-config generator the project populated using FetchContent ends up using the generator defaults.
For example with the CMakeLists.txt below and running cmake . -B build -G "Ninja Multi-Config" -DCMAKE_CONFIGURATION_TYPES=Release
the build system for eigen (under build/_deps/eigen-subbuild
) contains build-*.ninja
files for Debug, Release, and RelWithDebInfo configurations. If I use the "Visual Studio 17 2022" generator I get Debug, Release, RelWithDebInfo, and MinSizeRel configurations.
I repro with cmake 3.20 and 3.22.
Also a strange side note: if I do the configure within Visual Studio (either CMakeSetting.json or CMakePresets.json) then FetchContent does respect CMAKE_CONFIGURATION_TYPES and only the specified configuration types exist in the build output. (I only tried with VS 2022)
CMakeLists.txt
:
cmake_minimum_required (VERSION 3.8)
project ("repro")
include(FetchContent)
cmake_path(APPEND DEPS_DIR "${CMAKE_CURRENT_SOURCE_DIR}" "deps")
# Download Eigen.
set(EIGEN_VER 3.4.0)
FetchContent_Declare(eigen
URL https://gitlab.com/libeigen/eigen/-/archive/${EIGEN_VER}/eigen-${EIGEN_VER}.zip
SOURCE_DIR ${DEPS_DIR}/eigen)
FetchContent_MakeAvailable(eigen)
CMakePresets.json
:
{
"version": 3,
"configurePresets": [
{
"name": "ninja",
"generator": "Ninja Multi-Config",
"architecture": {
"value": "x64",
"strategy": "external"
},
"cacheVariables": {
"CMAKE_CONFIGURATION_TYPES": "Release"
}
}
]
}
Nope, that looks accurate to me
If only the C++ toolchain is installed I don't think you can get the VS version number (Brad mentioned "from a VS installation" so I assumed devenv.exe existed). Also would devenv.isolation.ini
be installed if you only have the C++ toolchain?
The only other ways I was told how to do this are using either VSWhere.exe or adding a dependency on the VS SDK and getting an ISetupConfiguration (but again if just the toolchain is installed I doubt either of those options would be available).
@brad.king devenv.isolation.ini
isn't likely to change, but it is considered an implementation detail so you probably don't want to rely on it. I believe the best option for CMake is to check the "File version" property of the devenv.exe
file.
I'm not familiar with that file but it does look like we use it internally to find the install id in a handful of places. I'll try to confirm if that's the recommended way.
No, I won't be able to do it for 3.22.
I'm still hesitant on having to manually specify the build directory though. In CI scenarios it would likely require parsing CMakePresets.json in order to get that build directory, which is a pain. Here's another thread that mentions the same problem: https://discourse.cmake.org/t/help-converting-most-of-a-ci-matrix-to-presets/3391/8.
Apologies for the late response (lots of people on vacation) but this is a known issue and should be fixed for the final release of VS 2022 17.0.
I agree, but at least it's a reasonable workaround for now that doesn't require manually editing the generated vcxproj.
Thanks for the heads up. I'm not familiar with these changes either but Olga's response sounds right to me:
Please open .vcxproj in a text editor and add the following property to the “Globals” property group
<PropertyGroup Label="Globals"> --- <ManagedAssembly>true</ManagedAssembly> </PropertyGroup>
I'm not sure if that means CMake will need a new variable to indicate this property is needed, but in the meantime is there a way to define your own global MSBuild properties? Does something like set_property(GLOBAL PROPERTY ManagedAssembly true)
work?
As I understand it the build preset refers to a configure preset solely for the purpose of grabbing the build directory (or enforcing some other contract like, as you mentioned, a build preset specifying a target that would only be available for a subset of configure presets). I agree in general most build presets are agnostic of the configure preset and removing the one-to-one mapping would be helpful.
From the IDE perspective I think it's okay for the build to fail if the build preset isn't valid for the specified configure preset. In fact isn't that possible already (e.g. specifying a target that isn't valid)? The IDE could attempt to help identify some of these problems but I don't think it would be foolproof. Ultimately I think it's the user's responsibility to author their presets correctly.
Erm, isn't that overcomplicating things? I'd just use
cmake --build <build-dir>
for that.
True, specifying build-dir
directly would work but it also requires knowing the build directory, which could be long or include macros. To me it seemed nice to have a solution that only required knowing the configure preset name (especially for scripts or in CI, as determining the build directory would likely require parsing and evaluating CMakePresets.json).
If I'm understanding the request (allowing a build preset to apply to more than one configure preset) then this shouldn't be a problem for Visual Studio. I think the following changes would provide a nice improvement to the build preset experience:
cmake --build --preset=my-build-preset
would be invalid since there isn't a deterministic configurePreset to get the binaryDir from, so we'd also need to...--configure-preset
flag to cmake --build
. This would allow differentiating the configure preset to use when the given build preset can apply to multiple configure presets. Also, we think it would be convenient if cmake --build --configure-preset=my-configure-preset
would also work as is, since it effectively eliminates the need for build presets in simple cases.In the context of the original issue, you could then have a build preset like
{
"name": "build-release",
"configurePreset": "*",
"configuration": "Release"
}
and would invoke it like cmake --build --preset=build-release --configure-preset=default-vs2019
. As an alternative (without needing to define any build presets) you could also just do cmake --build --configure-preset=default-vs2019 --config=Release
.
Thoughts? I realize this is different than the suggestion of treating the configurePresets like an inheritance chain. Perhaps I'm misunderstanding but wouldn't that still enforce a one-to-one mapping? The build preset would effectively map to the first configure preset that defines binaryDir (since that's the only thing the build preset really needs from the configure preset).
I asked about VCToolsVersion and the recommendation is still to use the props file instead. The issue CMake has is with the order of the imports: Microsoft.Cpp.Default.props should be imported before the toolset props file, not after.
I confirmed the names in the installer are incorrect and they'll be correct in the next preview. For what it's worth I can create and build a C++/CLI app without issues using the template provided in Visual Studio, so it might just be a CMake issue. I asked for some pointers on what might be wrong but I'm still waiting for a response.
Okay so I think I figured it out. The props file is just
<PropertyGroup>
<VCToolsVersion Condition = "'$(VCToolsVersion)' == ''" >14.27.29110</VCToolsVersion>
<VCToolsRedistVersion Condition = "'$(VCToolsRedistVersion)' == ''" >14.27.29016</VCToolsRedistVersion>
</PropertyGroup>
so indeed setting VCToolsVersion or VCToolsRedistVersion as an environment variable should override these settings. And checking my environment after running vcvars64.bat -vcvars_ver=14.27
gives me these relevant values:
VCToolsInstallDir=C:\Program Files (x86)\Microsoft Visual Studio\2019\d16.11\VC\Tools\MSVC\14.27.29110\
VCToolsRedistDir=C:\Program Files (x86)\Microsoft Visual Studio\2019\d16.11\VC\Redist\MSVC\14.29.30130\
VCToolsVersion=14.27.29110
Changing VCToolsRedistDir
to use the correct version (or clearing it completely) results in using the right toolset. My guess is somewhere MSBuild either uses VCToolsRedistDir
if it's defined or creates it based on VCToolsRedistVersion
, so setting either of those should work. I'll report a bug for the VCToolsRedistDir being incorrect.
As a recap:
Okay so strangely it looks like the right props file is being written to the generated MSBuild files (e.g. <Import Project="C:/Program Files (x86)/Microsoft Visual Studio/2019/d16.11/VC/Auxiliary/Build/14.27/Microsoft.VCToolsVersion.14.27.props" />
), however CMake prints out the wrong version during configure.
Just like above if I run either cmake --build
or msbuild
on the generated vcxproj from a plain non-developer command prompt the correct toolset is used. If it's from a developer prompt (with or without -vcvars_ver) it uses the wrong toolset. Maybe it's an MSBuild issue.
One more thing: when I create a project in VS and set the toolset it uses the VCToolsVersion MSBuild property instead of importing a props file. That vcxproj does use the correct toolset when building from a developer command prompt. I'm not sure when this property was introduced though.
I set a breakpoint in cmGlobalVisualStudio10Generator::SetGeneratorToolset and the right props file and version are being set (stacktrace below). As far as I can tell SetGeneratorToolset is the only place CMAKE_VS_PLATFORM_TOOLSET_VERSION is being set. Somewhere it must be getting overwritten or ignored but I'm pretty lost as to why. I tried searching for where the compiler is being identified (the part that prints out "The CXX compiler identification is ...") but found nothing :/
cmake.exe!cmGlobalVisualStudio10Generator::SetGeneratorToolset(const std::string & ts, bool build, cmMakefile * mf) Line 389 (c:\Users\safreed\source\repos\cmake\Source\cmGlobalVisualStudio10Generator.cxx:389)
cmake.exe!cmGlobalGenerator::EnableLanguage(const std::vector<std::string,std::allocator<std::string>> & languages, cmMakefile * mf, bool optional) Line 661 (c:\Users\safreed\source\repos\cmake\Source\cmGlobalGenerator.cxx:661)
cmake.exe!cmGlobalVisualStudioGenerator::EnableLanguage(const std::vector<std::string,std::allocator<std::string>> & lang, cmMakefile * mf, bool optional) Line 68 (c:\Users\safreed\source\repos\cmake\Source\cmGlobalVisualStudioGenerator.cxx:68)
cmake.exe!cmGlobalVisualStudio7Generator::EnableLanguage(const std::vector<std::string,std::allocator<std::string>> & lang, cmMakefile * mf, bool optional) Line 129 (c:\Users\safreed\source\repos\cmake\Source\cmGlobalVisualStudio7Generator.cxx:129)
cmake.exe!cmGlobalVisualStudio8Generator::EnableLanguage(const std::vector<std::string,std::allocator<std::string>> & lang, cmMakefile * mf, bool optional) Line 58 (c:\Users\safreed\source\repos\cmake\Source\cmGlobalVisualStudio8Generator.cxx:58)
cmake.exe!cmGlobalVisualStudio10Generator::EnableLanguage(const std::vector<std::string,std::allocator<std::string>> & lang, cmMakefile * mf, bool optional) Line 725 (c:\Users\safreed\source\repos\cmake\Source\cmGlobalVisualStudio10Generator.cxx:725)
cmake.exe!cmMakefile::EnableLanguage(const std::vector<std::string,std::allocator<std::string>> & lang, bool optional) Line 3510 (c:\Users\safreed\source\repos\cmake\Source\cmMakefile.cxx:3510)
cmake.exe!cmProjectCommand(const std::vector<std::string,std::allocator<std::string>> & args, cmExecutionStatus & status) Line 345 (c:\Users\safreed\source\repos\cmake\Source\cmProjectCommand.cxx:345)
cmake.exe!InvokeBuiltinCommand(bool(*)(const std::vector<std::string,std::allocator<std::string>> &, cmExecutionStatus &) command, const std::vector<cmListFileArgument,std::allocator<cmListFileArgument>> & args, cmExecutionStatus & status) Line 428 (c:\Users\safreed\source\repos\cmake\Source\cmState.cxx:428)
cmake.exe!cmState::AddBuiltinCommand::__l2::<lambda>(const std::vector<cmListFileArgument,std::allocator<cmListFileArgument>> & args, cmExecutionStatus & status) Line 438 (c:\Users\safreed\source\repos\cmake\Source\cmState.cxx:438)
cmake.exe!std::invoke<bool <lambda>(const std::vector<cmListFileArgument,std::allocator<cmListFileArgument>> &, cmExecutionStatus &) &,std::vector<cmListFileArgument,std::allocator<cmListFileArgument>> const &,cmExecutionStatus &>(cmState::AddBuiltinCommand::__l2::bool <lambda>(const std::vector<cmListFileArgument,std::allocator<cmListFileArgument>> &, cmExecutionStatus &) & _Obj, const std::vector<cmListFileArgument,std::allocator<cmListFileArgument>> & _Arg1, cmExecutionStatus & <_Args2_0>) Line 1610 (c:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.29.30037\include\type_traits:1610)
cmake.exe!std::_Invoker_ret<bool,0>::_Call<bool <lambda>(const std::vector<cmListFileArgument,std::allocator<cmListFileArgument>> &, cmExecutionStatus &) &,std::vector<cmListFileArgument,std::allocator<cmListFileArgument>> const &,cmExecutionStatus &>(cmState::AddBuiltinCommand::__l2::bool <lambda>(const std::vector<cmListFileArgument,std::allocator<cmListFileArgument>> &, cmExecutionStatus &) & _Func, const std::vector<cmListFileArgument,std::allocator<cmListFileArgument>> & <_Vals_0>, cmExecutionStatus & <_Vals_1>) Line 753 (c:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.29.30037\include\functional:753)
cmake.exe!std::_Func_impl_no_alloc<bool <lambda>(const std::vector<cmListFileArgument,std::allocator<cmListFileArgument>> &, cmExecutionStatus &),bool,std::vector<cmListFileArgument,std::allocator<cmListFileArgument>> const &,cmExecutionStatus &>::_Do_call(const std::vector<cmListFileArgument,std::allocator<cmListFileArgument>> & <_Args_0>, cmExecutionStatus & <_Args_1>) Line 920 (c:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.29.30037\include\functional:920)
cmake.exe!std::_Func_class<bool,std::vector<cmListFileArgument,std::allocator<cmListFileArgument>> const &,cmExecutionStatus &>::operator()(const std::vector<cmListFileArgument,std::allocator<cmListFileArgument>> & <_Args_0>, cmExecutionStatus & <_Args_1>) Line 968 (c:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.29.30037\include\functional:968)
cmake.exe!cmMakefile::ExecuteCommand(const cmListFileFunction & lff, cmExecutionStatus & status, std::optional<std::string> deferId) Line 457 (c:\Users\safreed\source\repos\cmake\Source\cmMakefile.cxx:457)
cmake.exe!cmMakefile::RunListFile(const cmListFile & listFile, const std::string & filenametoread, cmMakefile::DeferCommands * defer) Line 784 (c:\Users\safreed\source\repos\cmake\Source\cmMakefile.cxx:784)
cmake.exe!cmMakefile::Configure() Line 1736 (c:\Users\safreed\source\repos\cmake\Source\cmMakefile.cxx:1736)
cmake.exe!cmGlobalGenerator::Configure() Line 1244 (c:\Users\safreed\source\repos\cmake\Source\cmGlobalGenerator.cxx:1244)
cmake.exe!cmGlobalVisualStudio8Generator::Configure() Line 86 (c:\Users\safreed\source\repos\cmake\Source\cmGlobalVisualStudio8Generator.cxx:86)
cmake.exe!cmake::ActualConfigure() Line 2127 (c:\Users\safreed\source\repos\cmake\Source\cmake.cxx:2127)
cmake.exe!cmake::Configure() Line 1974 (c:\Users\safreed\source\repos\cmake\Source\cmake.cxx:1974)
cmake.exe!cmake::Run(const std::vector<std::string,std::allocator<std::string>> & args, bool noconfigure) Line 2356 (c:\Users\safreed\source\repos\cmake\Source\cmake.cxx:2356)
cmake.exe!`anonymous namespace'::do_cmake(int ac, const char * const * av) Line 359 (c:\Users\safreed\source\repos\cmake\Source\cmakemain.cxx:359)
cmake.exe!main(int ac, const char * const * av) Line 991 (c:\Users\safreed\source\repos\cmake\Source\cmakemain.cxx:991)
cmake.exe!invoke_main() Line 78 (d:\agent\_work\4\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:78)
cmake.exe!__scrt_common_main_seh() Line 288 (d:\agent\_work\4\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288)
cmake.exe!__scrt_common_main() Line 331 (d:\agent\_work\4\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:331)
cmake.exe!mainCRTStartup(void * __formal) Line 17 (d:\agent\_work\4\s\src\vctools\crt\vcstartup\src\startup\exe_main.cpp:17)
kernel32.dll!75e9fa29() (Unknown Source:0)
kernel32.dll![Frames below may be incorrect and/or missing, no symbols loaded for kernel32.dll] (Unknown Source:0)
ntdll.dll!76ec7a7e() (Unknown Source:0)
ntdll.dll!76ec7a4e() (Unknown Source:0)