microsoft / microsoft-ui-xaml

Windows UI Library: the latest Windows 10 native controls and Fluent styles for your applications
MIT License
6.34k stars 677 forks source link

CMake support for Win32 desktop apps #3863

Closed carlsound closed 1 year ago

carlsound commented 3 years ago

Will it be possible to use CMake to generate a Visual Studio solution for using C++/WinRT XAML UI's for Win32 desktop applications with 3.0?

huoyaoyuan commented 3 years ago

Since WinUI is a Windows only concept, what's the benefit of supporting CMake? The different between CMake and MSBuild is much more significant than .NET side. I think it's much harder than supporting .NET Core SDK.

carlsound commented 3 years ago

CMake simplifies generating a Visual Studio solution for a 3rd-party C++ SDK I use. Being able to use WinUI 3.0 with that work flow would be beneficial.

sylveon commented 3 years ago

@huoyaoyuan CMake is a very popular C++ build tool and MSBuild for C++ is horrific to deal with.

fredemmott commented 2 years ago

Following on from #4127

--

As for 'why cmake?':

fredemmott commented 2 years ago

I'm looking at making cmake invoke msbuild just for xaml generation

It looks like MarkupCompilePass2 depends on a ClCompile task being used for the app, so this doesn't seem workable. Could just be I don't understand how it's meant to work - it doesn't appear that the related targets fully describe their dependencies. For example, if I delete all generated files/build artifacts (e.g. git clean -ffdx), msbuild App1.vcxproj -target:MarkupCompilePass2 fails with XamlCompiler error WMC1007: Cannot resolve metadata for WinUI types. Please ensure that Nuget Restore was successful. - but the step completes fine if I skip the -target argument.

sylveon commented 2 years ago

Are you able to make cmake use the msbuild/Visual Studio generators? Would make things a lot easier, most likely.

I believe msbuild App1.vcxproj -target:MarkupCompilePass2 would work if you run a nuget restore or msbuild App1.vcxproj -target:Restore beforehand (since git clean removes the installed nuget packages, just like how e.g. you'd have to run npm i after a git clean to restore NPM packages)

fredemmott commented 2 years ago

Are you able to make cmake use the msbuild/Visual Studio generators? Would make things a lot easier, most likely.

In general, this still needs the targets to be defined in cmake, it just uses msbuild as an execution engine - or do you have something else in mind? I am using these generators (they're the default)

I believe msbuild App1.vcxproj -target:MarkupCompilePass2 would work if you run a nuget restore or msbuild App1.vcxproj -target:Restore beforehand

Even with the packages installed, it still fails - and the Restore target also fails, even though they're present:

msbuild App1.vcxproj -target:Clean;Restore;MarkupCompilePass2

   Determining projects to restore...
Restore:
  Nothing to do. None of the projects specified contain packages to restore.

... and then it fails the same way. I'm using the winui3 app template, and it seems the packages are installed a level higher than the vcxproj, but are referenced by it.

fredemmott commented 2 years ago

I resurrected my attempt at building only the xaml + idl stuff with msbuild, and have made some progress; I'll try to update my demo project later this week. Still a WIP, but just for reference in case my drive dies or something, this seems to be the magic set of targets needed to make this work:

ResolveReferences;_Midl;BuildGenerateSources;
MarkupCompilePass1;CppWinRTMakeProjections;
ComputeReferenceCLInput;MarkupCompilePass2

This at least from a cursory look gives me all the .g.h, .g.cpp, and .xbf files I expect.

Right now this gets me a partial build in my real app; next up:

As an aside, cmake has some xaml and IDL support - but for modern stuff, it's limited to c++/cx, not C++/winrt; there's some legacy IDL support for standard C++.

bhardwajs commented 2 years ago

As an aside, cmake has some xaml and IDL support - but for modern stuff, it's limited to c++/cx, not C++/winrt; there's some legacy IDL support for standard C++.

@fredemmott - If you have a well-defined list of asks for CMake support, I can contribute this to upstream CMake. No promises though :)

fredemmott commented 2 years ago

Thanks; the first thing would be support for IDL files -> MIDL sections in the vcxproj; with how cppwinrt and the xaml build tasks are linked, custom targets don’t work for this. I can’t give an accurate complete list as the lack of IDL support really blocks digging into it further.

the ideal end result would be:

add_executable(
  MyApp
  WIN32
  app.xaml
  app.idl
  app.xaml.cpp
  window.xaml
  window.idl
  window.xaml.cpp
  page.xaml
  page.idl
  page.xaml.cpp
)

Then:

AIUI the existing VS_GLOBAL_foo property should be usable for setting the windows app sdk, bootstrap, packed/unpackaged, self contained options.

Nice to have: integrate self contained deployment with install(RUNTIME_DEPENDENCY_SET) and $; if cmake is too limited (it seems to expect a flat list of DLLs whereas the app SDK expects a hierarchy), provide some custom way to make running from build dir work and install the files in self contained mode

fredemmott commented 2 years ago

VS_PACKAGE_REFERENCES

Microsoft's docs say "C++ and JavaScript project types are unsupported."; I thought I read recently that this had changed, but I'm unable to find a source or test right now.

fredemmott commented 2 years ago

VS_PACKAGE_REFERENCES

Microsoft's docs say "C++ and JavaScript project types are unsupported."; I thought I read recently that this had changed, but I'm unable to find a source or test right now.

https://github.com/microsoft/react-native-windows/issues/8171 indicates it's supported now

fredemmott commented 2 years ago

@bhardwajs ignore me for now; some of these may have been fixed with CMake 3.23 which came out in April; I was still on 3.22. Will experiment and report back :)

bhardwajs commented 2 years ago

@bhardwajs ignore me for now; some of these may have been fixed with CMake 3.23 which came out in April; I was still on 3.22. Will experiment and report back :)

Sounds good. Thanks!

fredemmott commented 2 years ago

This is much closer to working out the box with the changes in CMake 3.23; the next problem is that the msbuild targets/props and cmake seem to disagree on how ProjectDir, OutDir, and IntDir relate:

Example config (full project]:

add_executable(
  DemoApp
  WIN32
  app.manifest
  App.xaml.cpp App.xaml App.idl
  MainWindow.xaml.cpp MainWindow.xaml MainWindow.idl
)
set_property(
  SOURCE App.xaml
  PROPERTY VS_XAML_TYPE
  "ApplicationDefinition"
)
set_property(
  TARGET DemoApp
  PROPERTY VS_PACKAGE_REFERENCES
  "Microsoft.Windows.CppWinRT_2.0.220608.4"
  "Microsoft.WindowsAppSDK_1.1.1"
  "Microsoft.Windows.SDK.BuildTools_10.0.22621.1"
)
set_target_properties(
  DemoApp
  PROPERTIES
  CXX_STANDARD 20
  CXX_STANDARD_REQUIRED ON
  CXX_EXTENSIONS OFF
  VS_GLOBAL_RootNamespace DemoApp
  VS_GLOBAL_AppContainerApplication false
  VS_GLOBAL_AppxPackage false
  VS_GLOBAL_CppWinRTEnabled true
  VS_GLOBAL_CppWinRTOptimized true
  VS_GLOBAL_CppWinRTRootNamespaceAutoMerge true
  VS_GLOBAL_UseWinUI true
  VS_GLOBAL_ApplicationType "Windows Store"
  VS_GLOBAL_WindowsPackageType None
  VS_GLOBAL_EnablePreviewMsixTooling true
  # Skip the "ResolveNuGetPackageAssets" target as it fails with an empty sequence error
  # Not certain why - I *think* that none of the packages we use have assets
  VS_GLOBAL_ResolveNuGetPackages false
)

This seems to set up things where C++/WinRT runs, knows of the xaml files etc; it fail with:

"c:\Users\Fred\code\cmake-cpp-winrt-winui3\build\src\DemoApp.vcxproj" (default target) (1) ->
(Midl target) ->
  midlrt : error MIDL2212: [msg]error while writing to file [context]App.winmd (HRESULT:0x80070003 - The system cannot
find the path specified. ) [c:\Users\Fred\code\cmake-cpp-winrt-winui3\build\src\DemoApp.vcxproj]

    2 Warning(s)
    1 Error(s)

Time Elapsed 00:00:00.83

If I look at the msbuild binlogs, I see that:

C:\Program Files (x86)\Windows Kits\10\bin\10.0.22000.0\x64\midl.exe /metadata_dir "C:\Program Files (x86)\Windows Kits\10\References\10.0.22000.0\windows.foundation.foundationcontract\4.0.0.0" /winrt /W1 /nologo /char signed /env x64 /out"c:\Users\Fred\code\cmake-cpp-winrt-winui3\build\src\/DemoApp.dir\Debug\\" /winmd "DemoApp.dir\Debug\Unmerged\App.winmd" /h "App.h" /dlldata "nul" /iid "App_i.c" /proxy "App_p.c" /notlb /client none /server none /enum_class /ns_prefix /target "NT60"  /nomidl @"c:\Users\Fred\code\cmake-cpp-winrt-winui3\build\src\DemoApp.dir\Debug\DemoApp.vcxproj.midlrt.rsp" "C:\Users\Fred\code\cmake-cpp-winrt-winui3\src\App.idl" 

If I replace /winmd "DemoApp.dir\Debug\Unmerged\App.winmd" with /winmd "DemoApp.dir\Debug\DemoApp.dir\Debug\Unmerged\App.winmd" I can run midl.exe by hand without error, but later steps fail with similar confusion.

The cmake-generated vcxproj looks like IntDir/OutDir are set reasonably - full generated vcxproj here: https://gist.github.com/fredemmott/25f257f9504da747b896abf1d0ffea8c

    <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">C:\Users\Fred\code\cmake-cpp-winrt-winui3\build\src\Debug\</OutDir>
    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">DemoApp.dir\Debug\</IntDir>

I've not been able to track down precisely where this is doubling up; it could be C++/winrt's msbuild files, the Midl code in Microsoft.CppBuild.targets, or maybe cmake's generating a subtly invalid vcxproj?

bhardwajs commented 2 years ago

Is there a dependency on Directory.Build.Props somewhere and are you doing out-of-source build (recommended) in CMake? If so, there is the issue of CMake not working very well with Directory.Build.Props.

fredemmott commented 2 years ago
fredemmott commented 2 years ago

Compared to a working project started from the winui template, saw that <Midl><OutputDirectory> is not specified; if I remove <OutputDirectory>$(ProjectDir)/$(IntDir)</OutputDirectory> manually from the vcxproj, I can proceed past this step.

Next up:

"c:\Users\Fred\code\cmake-cpp-winrt-winui3\build\src\DemoApp.vcxproj" (default target) (1) ->
(CppWinRTMakePlatformProjection target) ->
  C:\Users\Fred\.nuget\packages\microsoft.windows.cppwinrt\2.0.220608.4\build\native\Microsoft.Windows.CppWinRT.targets
(661,9): error MSB3073: The command ""C:\Users\Fred\.nuget\packages\microsoft.windows.cppwinrt\2.0.220608.4\build\nativ
e\"cppwinrt @"DemoApp.dir\Debug\DemoApp.vcxproj.cppwinrt_plat.rsp"" exited with code 9009. [c:\Users\Fred\code\cmake-cp
p-winrt-winui3\build\src\DemoApp.vcxproj]

not seeing an obvious cause for that yet, suspect it might be related to VS_GLOBAL_ResolveNuGetPackages false

fredemmott commented 2 years ago

Okay, got that one:

  VS_GLOBAL_CppWinRTEnabled true

I need to remove this from CMake: it is mutually exclusive with CppWinRTPackaged, which I want instead, and is automatically set if !CppWinRTEnabled.

<Midl>
  <OutputDirectory>

This needs removing and I think it needs changes to CMake - https://gitlab.kitware.com/cmake/cmake/-/issues/21668 might be one approach (an additional target property) might be one approach

Next problems appear to be "my C++ code is invalid" rather than build system so I'll poke some more and hopefully it's just the midl/outputdirectory change that's needed

fredemmott commented 2 years ago
[build] C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Microsoft\VC\v170\Microsoft.CppBuild.targets(502,5):

warning MSB8021: The value 'MultiByte' of the variable 'CharacterSet' is incompatible
with the value 'true' of the variable 'WindowsAppContainer'.

Doesn't block the build, but it's not overridable and I'm guessing it's incompatible because it may cause issues. The generator sets it depending on the target type.

fredemmott commented 2 years ago

Here we go: https://github.com/fredemmott/cmake-cpp-winrt-winui3

It's undocumented, but basically just needs... a lot of digging through msbuild and cmake stuff to figure out what properties etc are needed.

Requires CMake 3.23 (April 2022) and Windows App SDK 1.1 (June 2022) - i.e. this has only became possible in the last ~ 2 weeks.

image

Key components

Issues/requests for improvement in cmake handling

I'm not planning on looking at these; I think I'm done on cmake/msbuild yak shaving for now, and this unblocks me cleaning up my real app (which currently has cmake shell out to msbuild)

PCH error

[build] Generated Files\XamlTypeInfo.Impl.g.cpp(631,1): fatal error C1010: unexpected end of file while looking for precompiled header. Did you forget to add '#include "C:/Users/Fred/code/cmake-cpp-winrt-winui3/build/src/CMakeFiles/DemoApp.dir/Debug/cmake_pch.hxx"' to your source? [C:\Users\Fred\code\cmake-cpp-winrt-winui3\build\src\DemoApp.vcxproj]
fredemmott commented 2 years ago

ResolveNuGetPackages

I also have this problem with pure msbuild in the unpackaged app template if I replace packages.config and the target import with:

  <ItemGroup>
    <PackageReference Include="Microsoft.Windows.CppWinRT" Version="2.0.220608.4" />
    <PackageReference Include="Microsoft.WindowsAppSDK" Version="1.1.1" />
    <PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.1" />
    <PackageReference Include="Microsoft.Windows.ImplementationLibrary" Version="1.0.220201.1" />
  </ItemGroup>
sylveon commented 2 years ago

cppwinrt has had work to enable being used with a PackageReference, but I'm not sure about the other NuGet packages here.

However, WIL can be brought in using vcpkg instead of NuGet, and the SDK build tools NuGet can be entirely ignored as long as the Windows SDK build 22621 is installed locally.

fredemmott commented 2 years ago

Yeah, a few issues to work around; sadly several of the core “ modern Microsoft projects - including the windows app sdk - are only supported via nuget; externalprojevt_add and directory props/targets may be a more reliable to deal with them.

vcpkg can be handy, but AIUI it’s primarily intended for each system to have a active version of a dependency for all projects they want to build, instead of projects being able to specify version constraints that the package manager satisfies just for that project?

it feels like there’s really two C++ package management camps at microsoft: nuget is the future, and vcpkg is the future. Neither had complete support across tooling.

https://developercommunity.visualstudio.com/t/use-packagereference-in-vcxproj/351636 Seems the most detailed resource about vcxproj project references - it’s definitely not “supported now” as I thought :(

sylveon commented 2 years ago

it feels like there’s really two C++ package management camps at microsoft: nuget is the future, and vcpkg is the future. Neither had complete support across tooling.

What I'm seeing is WinDiv prefers NuGet, while DevDiv prefers vcpkg. But since DevDiv owns NuGet, and for them the future of C++ package management is vcpkg, they do not seem to be making efforts towards making NuGet better to publish for and consume from C++

fredemmott commented 2 years ago

These props fix the package references - again specifying them via VS_GLOBAL does not appear to work:

<Project>
   <PropertyGroup>
     <RuntimeIdentifiers>win10-x64</RuntimeIdentifiers>
     <NuGetTargetMoniker>native,Version=v0.0</NuGetTargetMoniker>
   </PropertyGroup>
 </Project>

In my real app, I also need win-x64 (not win10) as a runtime identifier; it looks like this might be added when I add a .rc file to my sources.

fredemmott commented 2 years ago

My real app now seems to basically be working with this :)

Extra things:

Combined with https://github.com/microsoft/microsoft-ui-xaml/issues/3863 there's lots of small improvements that could be made to CMake to make things a little better, but making winui3 with c++/winrt actually 'good' would require big changes across multiple MS projects - either:

fredemmott commented 2 years ago

@bhardwajs one more thing: Not-self-contained builds depend on msbuild rules adding the bootstrapping libraries to <Link><AdditionalDependencies>, and this doesn't work in cmake (I've stopped using self-contained mode because of #7281)

CMake's MSBuild generator completely overrides AdditionalDependencies, removing these; the normal thing in msbuild outside of cmake seems to be to set it to <AdditionalDependencies>foo.lib;%(AdditionalDependencies)</AdditionalDependencies> instead of cmake's approach of <AdditionalDependencies>foo.lib</AdditionalDependencies>

For now, I'm working around this by adding %(AdditionalDependencies) to CMAKE_CXX_STANDARD_LIBRARIES. This feels brittle, and interferes with standard toolchain configuration. I also tried an IMPORTED INTERFACE library, however the msbuild generators appear to ignore SUFFIX and IMPORTED_SUFFIX, and always append ".lib" - it would also feel pretty brittle that %(AdditionalDependencies) works as an IMPORTED_LIBNAME

I suggest modifying cmake to do one out of:

Of course, it would be better if WinUI3 and the Windows App SDK supported and documented systems other than msbuild :p

github-actions[bot] commented 1 year ago

This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 5 days.