WindowsAppSDK 1.6 increases the build time drastically.
Describe the bug
With last VS update last week I updated my C# .NET8 solution from WindowsAppSDK 1.5 to WindowsAppSDK 1.6.
Unfortunately this increased the build time drastically: e.g. a project which includes a WindowsAppSDK shared project now takes at least 3 times as long to build as before. Obviously this increased turn around time drastically and slows down the development cycle. I never experienced something like at on any prior WindowsAppSDK update.
How could I go back to the prior building cycle turn arounds?
I also experienced - but this is nothing new though - that building the solution again without something being changed (!) e.g. just by pressing F5 goes through new compiler runs. Why are the building tools (compiler etc.) not aware that nothing was changed?
Please don't ask me for a repo. Demonstrating the issue requires a solution with a substantial number of source files.
Steps to reproduce the bug
Please see above
Expected behavior
No response
Screenshots
No response
NuGet package version
None
Packaging type
No response
Windows version
No response
IDE
No response
Additional context
No response
Just to ask, since it may be obvious for you but not for me. Is this a C++ project or a C# project? I think the "VS update last week" implies C#, but it would be nice to have the language explicitly stated.
Just to ask, since it may be obvious for you but not for me. Is this a C++ project or a C# project? I think the "VS update last week" implies C#, but it would be nice to have the language explicitly stated.
@DarranRowe sorry I forgot to be specific and just amended the case description: this is C# on .NET8
At least for the build time change, this might be related to our source generator / analyzer in the Windows SDK projection having a perf impact on larger projects. We do have an updated Windows SDK projection package coming in next month's .NET SDK update with some performance improvements to the generator that I am hoping may help here. If you want to try out the change manually before it is in the .NET SDK, see here for the WindowsSdkPackageVersion to specify.
Thanks for your feedback @manodasanW.
Honestly I have no clue why I would need source generators / analyzers or even any "projection" on building (!) a solution.
Isn't there a way to get rid of all that? If so, what exactly would I need to do?
BTW I have these settings since ever, because I don't need all that stuff
Thanks for your feedback @manodasanW.
Honestly I have no clue why I would need source generators / analyzers or even any "projection" on building (!) a solution.
Isn't there a way to get rid of all that? If so, what exactly would I need to do?
For the projection, the answer is yes, but you honestly don't want to do that.
The Windows App SDK is a set of Windows Runtime components. Windows Runtime components are an evolution of COM. The projection is to make them much easier to use by mapping them to a more natural construct for the language. Without the projection, the components would have to be accessed via the ABI, which is more akin to accessing COM components.
I have had to do that in a C++ project, there was a need to access some Windows App SDK functionality but I was unable to use C++/WinRT (this is the recommended C++ projection). The equivalent of the following:
auto dispqueuec = winrt::Microsoft::UI::Dispatching::DispatcherQueueController::CreateOnCurrentThread();
ended up being:
template <typename Interface, typename RuntimeClass>
struct interface_traits
{
static_assert(static_assert_helper<Interface>::value, "interface_traits is not specialised for this interface");
};
template <typename Interface, typename RuntimeClass>
struct factory_interface_traits
{
static_assert(static_assert_helper<Interface>::value, "factory_interface_traits is not specialised for this interface");
};
template <>
struct interface_traits<ABI::Microsoft::UI::Dispatching::IDispatcherQueueController, ABI::Microsoft::UI::Dispatching::DispatcherQueueController>
{
using type = ABI::Microsoft::UI::Dispatching::IDispatcherQueueController;
using base_type = ABI::Microsoft::UI::Dispatching::IDispatcherQueueController;
using class_type = ABI::Microsoft::UI::Dispatching::DispatcherQueueController;
inline static constexpr std::wstring_view class_name{ init_stringview(RuntimeClass_Microsoft_UI_Dispatching_DispatcherQueueController) };
inline static constexpr std::wstring_view interface_name{ init_stringview(InterfaceName_Microsoft_UI_Dispatching_IDispatcherQueueController) };
inline static constexpr bool activatable = false;
};
template <>
struct factory_interface_traits<ABI::Microsoft::UI::Dispatching::IDispatcherQueueControllerStatics, ABI::Microsoft::UI::Dispatching::DispatcherQueueController>
{
using type = ABI::Microsoft::UI::Dispatching::IDispatcherQueueControllerStatics;
using class_type = ABI::Microsoft::UI::Dispatching::DispatcherQueueController;
inline static constexpr std::wstring_view class_name{ init_stringview(RuntimeClass_Microsoft_UI_Dispatching_DispatcherQueueController) };
inline static constexpr std::wstring_view interface_name{ init_stringview(InterfaceName_Microsoft_UI_Dispatching_IDispatcherQueueControllerStatics) };
};
inline Microsoft::WRL::Wrappers::HString initialise_hstring(const std::wstring_view &runtime_class)
{
HRESULT hr = S_OK;
Microsoft::WRL::Wrappers::HString hs;
hr = hs.Set(runtime_class.data());
RoOriginateErrorW(hr, 0, L"Failed to set the runtime class name");
THROW_IF_FAILED(hr);
return hs;
}
template <typename Interface, typename RuntimeClass>
inline auto get_activation_factory()
{
HRESULT hr = S_OK;
Microsoft::WRL::ComPtr<Interface> ret;
auto runtime_class = initialise_hstring(factory_interface_traits<Interface, RuntimeClass>::class_name);
hr = Windows::Foundation::GetActivationFactory(runtime_class, ret.ReleaseAndGetAddressOf());
if (FAILED(hr))
{
#if (__cplusplus >= 202002L || (defined _MSVC_LANG && _MSVC_LANG >= 202002L))
auto l_fmtstr = application::helper::format_to_string(L"Failed to get activation factory. Interface name: {}, RuntimeClass name: {}", factory_interface_traits<Interface, RuntimeClass>::interface_name, factory_interface_traits<Interface, RuntimeClass>::class_name);
#else
auto l_fmtstr = application::helper::format_to_string(L"Failed to get activation factory. Interface name: %s, RuntimeClass name: %s", factory_interface_traits<Interface, RuntimeClass>::interface_name.data(), factory_interface_traits<Interface, RuntimeClass>::class_name.data());
#endif
RoOriginateErrorW(hr, static_cast<UINT>(l_fmtstr.length()), l_fmtstr.c_str());
THROW_IF_FAILED(hr);
}
return ret;
}
...
//Create dispatcher queue function
...
ComPtr<IDispatcherQueueController> disp_queue_ctrl;
auto dqcs = wrl_helpers::get_activation_factory<IDispatcherQueueControllerStatics, DispatcherQueueController>();
THROW_IF_FAILED(dqcs->CreateOnCurrentThread(disp_queue_ctrl.ReleaseAndGetAddressOf()));
...
I could have made it more compact if it was just using the ABI to access the Windows App SDK DispatcherQueue, but I was using some other Windows Runtime components through the ABI so I just added onto what I had.
This highlights two important things. First, using these components through the ABI requires a lot of code, and a lot of it is shared. Secondly, the components are usable for multiple languages.
@DarranRowe thanks for your feedback.
Here is my layman's thinking: What you said is well and fine. Sure, I wanted to have easy-to-use 'wrappers' in my C# environment. But:
- the WindowsAppSDK XX.YY is 'static' after installation (aka won't change). So, spending the effort once (!) after upgrade should be sufficient. Why would that negatively impact every (!) build run?
- why would it impact building (!) the project in the first place? I needed those 'wrappers' when coding - e.g. through IntelliSense - and in fact there are there already (apparently). So, why spending the effort again on build?
Also, I'm sure there must have been something to the same effect with <= WindowsAppSDK 1.5. Why would WindowsAppSDK 1.6 performance be worse in that regard?
I finally found the bandwidth to dig a bit deeper. After upgrading to .NET9 I took a look on what's happening when doing 'Right click on the project -> Build' while absolutely nothing has changed on the sources.
Here is an output using verbosity = 'Normal' (I could provide more detailed logs if needed)
output.zip
The culprit are these compile steps which I highlighted in the screenshot below. Why are they executed on every build as nothing has changed?
Hi @DierkDroth,
You can use msbuild -bl switch (msbuild MyProject.proj -bl) to generate a binary log file (.binlog) and share it out. For details, visit this document. Once .binlog file is provided, we can try to figure out what MSbuild targets contribute most to the build slowness.
@LegendaryBlair thanks for getting back to me.
To clarify: I´m referring to the build process started from within VisualStudio (e.g. pressing F5) and not by an external msbuild run. Sure, I could setup a batch file to build the solution and create a binlog using msbuild, but this does not reflect the actual situation.
How could I create a binlog when building the solution from within VisualStudio (e.g. pressing F5)?
Hi @DierkDroth,
I believe, if some target like MarkupCompilePass1 is slow, build inside visual studio should not have much difference than build from command line. Pressing F5 experience could be another story as it may do more things like app deployment.
If you want to generate .binlog file within Visual Studio, please follow these steps:
- Install the Project System Tools Extension: This extension is available for Visual Studio 2017, 2019, and 2022. You can find it in the Visual Studio Marketplace and install it directly from Visual Studio.
-
Open the Build Logging Tool Window: Once the extension is installed, go to
View > Other Windows > Build Loggingto open the Build Logging tool window. -
Start Logging Builds: In the Build Logging tool window, click the
Start logging buildsbutton. This will start capturing the build logs for any projects you build in Visual Studio. -
Build Your Project: Now, build your project as you normally would by clicking the
Buildbutton or using theBuildmenu. -
Stop Logging and Save the .binlog File: After the build is complete, go back to the Build Logging tool window and click the
Stop logging buildsbutton. You will be prompted to save the .binlog file, which contains the detailed build logs.
This process will generate a .binlog file that you can use for troubleshooting and analysis, similar to how you would generate it using the command line with msbuild Myproject.csproj /bl
@LegendaryBlair thanks again for your feedback
This process will generate a .binlog file that you can use for troubleshooting and analysis, similar to how you would generate it using the command line with msbuild Myproject.csproj /bl
I'm not sure we're on the same track here. My report is not about compile steps which supposedly are not needed when running msbuild, but when running a build by pressing F5 - which is what I'm doing in my edit - compile - run - edit cycles.
Pressing F5 experience could be another story as it may do more things like app deployment.
To clarify: The delay on deployment is not my concern. However, the unneeded compile steps when pressing F5 are...
@LegendaryBlair could you please clarify if the steps which you proposed are equivalent to the scenario where I'm experiencing the delay (building by F5)?
Thanks in advance
@DierkDroth Sorry for the delay response, I'm just back from holiday.
Honestly speaking, I'm not 100% sure if the slowness in MSBuild target execution MarkupCompilePass1 would be the same case when using msbuild in the command line. But I believe it's worth to have a try with my initial comment below and provide me the .binlog file.
Hi @DierkDroth, You can use msbuild -bl switch (
msbuild MyProject.proj -bl) to generate a binary log file (.binlog) and share it out. For details, visit this document. Once.binlogfile is provided, we can try to figure out what MSbuild targets contribute most to the build slowness.
@LegendaryBlair thanks for getting back to me.
OK, I created a BINLOG (using a BAT file I already had in place) and also attached a trace output using verbosity = 'Diagnostic'.
Hope this helps. Please let me know your findings. Thanks
@LegendaryBlair I found the issue: there was a custom prebuild step which basically touched a resource file, although the content of the resource file was not changed. I finally isolated the problem as I restructured my solution file ...
I'm sorry for the noise :-(. Please accept my apologies.