Visual Studio generator introduces unnecessary references between static libraries harming parallel compilation
Currently, if you have CMakeLists.txt
add_library(A OBJECT a.cpp)
add_library(B OBJECT b.cpp)
target_link_libraries(B PUBLIC A)
add_executable(Main main.cpp)
target_link_libraries(Main PRIVATE B)
It will result in generated project B having a reference to project A. MSBulid uses references as dependency indication (for ordering) and therefore will not compile B until all A is built. On big projects (as in our current production) this may significantly increase compilation times up to 10 times (we've measured that by introducing a custom framework on top of add_library
et co, basically performing manual dependency tracking and application of includes, etc)
As indicated in #13799 (closed) discussion cmake have to do this to facilitate possible add_custom_command
PRE/POST_BUILD
events and other special generators (microsoft resource compiler?) that may be required to be run before any dependent projects could be built.
My suggestion would be to introduce a custom logic checking dependency chain and only introducing references to points in the dependency chain that do contain aforementioned elements, skipping intermediate nodes - custom commands, special generators, etc (I believe Ninja does something like that already and I saw the MR here before, but I failed to find it again).
I.e. with
add_custom_target(Custom COMMAND ...)
add_library(A OBJECt a.cpp)
add_library(B OBJECt b.cpp)
target_link_libraries(B PUBLIC A)
add_dependency(A Custom)
add_executable(Main main.cpp)
target_link_libraries(Main PRIVATE B)
Generated project A
would have reference to Custom
, project B
also to Custom
, but NOT A
; and finally Main
would depend on B
and A
Basically the idea is that for each target we check its type and properties and assign a target kind for it - either "full" or "transient". When building list of references to include, we use following logic (where target
is the target we generate project for and dependency
is target we are currently evaluating for inclusion):
- If target is
full
, all of its immediate dependencies are included as references with no additional logic - If dependency is
full
, it's included as reference as-is - If dependency is
transient
, we skip the dependency itself, but instead fetch its references recursively using same function
For features allowed in transient
targets I would list (for now)
- C or C++ languages only (because I have no idea about C#)
- Any compilation flags and features are allowed
- PCH is allowed
Everything else (WinRT, non-C/C++ sources, including resource files and such, AUTOMOC and other similar generative features) is disallowed. If you have other features to add to the list, please do so
If no one have issues with the above, I am willing to contribute and send an MR.