Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AOT] Clean up AOT build issue in Common.UI #36376

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open

Conversation

moooyo
Copy link
Contributor

@moooyo moooyo commented Dec 17, 2024

Summary of the Pull Request

  • Remove UseWPF and UseWindowsForms properties from Common.UI.csproj
  • Modify OpenSettings method in SettingsDeepLink.cs to use System.AppContext.BaseDirectory
  • Mark this csproj as AOT compatible.

PR Checklist

  • Closes: #xxx
  • Communication: I've discussed this with core contributors already. If work hasn't been agreed, this work might be rejected
  • Tests: Added/updated and all pass
    I have tested this PR in my local env.
  • Localization: All end user facing strings can be localized
  • Dev docs: Added/updated
  • New binaries: Added on the required places
  • Documentation updated: If checked, please file a pull request on our docs repo and link it here: #xxx

Detailed Description of the Pull Request / Additional comments

Let me explain why we need this change.

  1. Currently, we can not enable AOT in a project which are using WPF and WindowsForm. https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/incompatibilities
  2. It seems that we don't need to import WPF and WindowsForm in this infra project. So, I removed it and tested. All things seems well.
  3. If we want to mark this project is AOT compatible, we need to remove reflection usage.

Validation Steps Performed

@moooyo moooyo requested review from snickler and crutkas December 17, 2024 06:29
@moooyo moooyo added Don't merge - Hold for release Hold off on merging this PR, even if it's approved. Needs-Review This Pull Request awaits the review of a maintainer. labels Dec 17, 2024
@moooyo moooyo marked this pull request as ready for review December 17, 2024 06:36
@moooyo
Copy link
Contributor Author

moooyo commented Dec 17, 2024

/azp run

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

Copy link

@yeelam-gordon yeelam-gordon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall, just a minor comments.

src/common/Common.UI/SettingsDeepLink.cs Outdated Show resolved Hide resolved
<PropertyGroup>
<UseWPF>true</UseWPF>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jaimecbernardo - Did we have to add the UseWPF and UseWindowsForms in this project for the alignment of the dll versions? I can't remember if it was THIS one or not.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, This was the one. Well remembered. That's why CI isn't passing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh...sad news. Any suggestion? I will investigate how to align the dll version. If any suggestion ,please let me know.

@jaimecbernardo

@crutkas crutkas added In for .88 Good to merge after release and removed Needs-Review This Pull Request awaits the review of a maintainer. labels Dec 18, 2024
Copy link
Collaborator

@jaimecbernardo jaimecbernardo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI isn't passing because different dll versions for the same dependencies are being used. This is one of the reasons why both WPF and UseWindowsForms are in Common.UI

@jaimecbernardo jaimecbernardo removed Don't merge - Hold for release Hold off on merging this PR, even if it's approved. Good to merge after release In for .88 labels Dec 18, 2024
@moooyo
Copy link
Contributor Author

moooyo commented Dec 19, 2024

CI isn't passing because different dll versions for the same dependencies are being used. This is one of the reasons why both WPF and UseWindowsForms are in Common.UI

@jaimecbernardo Could you please give me more context about why we need to do this check?

I did some investigate and find out those dll (such as System.Drawing.dll) ref from "runtimepack.Microsoft.NETCore.App.Runtime.win-x64/9.0.0" and seems that it's hard to specific a version for those dll.

Thoese assemblyVersion are the same (4.0.0.0) but the fileVersion are different.

@jaimecbernardo
Copy link
Collaborator

Hi @moooyo , This requires some context indeed 😄
Before #27451 , every application resided on their own folders. Since we do the applications as self-contained, this means we would end up having duplicated library files all over the place, since every application would have their own WinAppSDK dlls and .NET dlls. We tried doing things like deep links to try to save on installed disk space, but when users clicked the File Explorer properties for PowerToys, it still showed 3.10 GB.
So we've done some work to try to have every application in the same folder. This reduced the size reported by File Explorer to 538 MB at that time ( a ~83% reduction in reported size!!! )
(Actually WinUI 3 apps are on their own separate folder since we never figured how to make those work together, since WebView2 dlls at the time reported the same version but were incompatible between WinUI 3 and the version directly referenced by our preview handlers. Still, used disk space reduced a lot.)
Keeping every application in the same runtime folder meant every dll should be the one that's expected by the applications, or we could have runtime crashes (we had runtime crashes where one application or another would work depending on build order and which application would manage to copy their dependency dll last). So we decided that dll name reported in deps.json files and fileVersion would be what we would use.

When we upgraded to .NET 9, different .NET dlls where shipped on the 9.00 SDKs with different file versions. Here's a snippet of my analysis at the time. Not even assembly Versions match.

So, "runtimepack.Microsoft.NETCore.App.Runtime.win-x64/9.0.0" ships with 
          "Microsoft.VisualBasic.dll": {
            "assemblyVersion": "10.0.0.0",
            "fileVersion": "9.0.24.52809"
          },
          "System.Drawing.dll": {
            "assemblyVersion": "4.0.0.0",
            "fileVersion": "9.0.24.52809"
          },
          "WindowsBase.dll": {
            "assemblyVersion": "4.0.0.0",
            "fileVersion": "9.0.24.52809"
          },

While "runtimepack.Microsoft.WindowsDesktop.App.Runtime.win-x64/9.0.0" ships with
          "Microsoft.VisualBasic.dll": {
            "assemblyVersion": "10.1.0.0",
            "fileVersion": "9.0.24.52901"
          },
          "System.Drawing.dll": {
            "assemblyVersion": "9.0.0.0",
            "fileVersion": "9.0.24.52901"
          },
          "WindowsBase.dll": {
            "assemblyVersion": "9.0.0.0",
            "fileVersion": "9.0.24.52902"
          },
It's not clear to me why PowerToysColorPickerUI would pick System.Drawing.dll from "runtimepack.Microsoft.NETCore.App.Runtime.win-x64/9.0.0" and WindowsBase.dll from "runtimepack.Microsoft.WindowsDesktop.App.Runtime.win-x64/9.0.0"
And then PowerToys.GcodeThumbnailProvider picks System.Drawing.dll from "runtimepack.Microsoft.WindowsDesktop.App.Runtime.win-x64/9.0.0" and Windows Base.dll from "runtimepack.Microsoft.NETCore.App.Runtime.win-x64/9.0.0"

So on the .NET 9 Upgrade PR, we made everything depend on WPF and WinForms to make sure the latest dll version would be picked for every dependency.
04dc5ce

I hope this helps.

@moooyo
Copy link
Contributor Author

moooyo commented Dec 19, 2024

@jaimecbernardo Thanks for your reply. That's a amazing work.
In mu understanding, we do need to let UseWPF and UseWinForms stay in the csproj unless we find a way to solve this conflict.

Make a exception (add some change to let them use different dll) maybe a choice, but I don't wan to solve by this way. If we add one exception, I believe there will be more and more exceptions...

So, how about adding another props file to do the same thing? I mean we can create a props file and add UseWPF in this file. Then let every csproj file import it. By this way, we can keep the aot compatibility of common.UI logic code and avoid the new code to break it.

@moooyo
Copy link
Contributor Author

moooyo commented Dec 19, 2024

Currently, most of our modules use WPF or WindowsForm to implement UI. We can not make these publish with AOT enabled.
But some are not, such as AdvancedPaste and settings.UI. For these project, we have the possibility to make them publish with AOT.

That's why I want to remove UseWPF and UseWindowsForms in common.UI.

@jaimecbernardo
Copy link
Collaborator

@moooyo , you can give it a try, but I think you'll likely still get these conflicts on different applications reporting using different dll versions 🤔
By the way, here's an incantation I run locally on PowerShell when I'm trying to solve this issues. It's basically the script that runs in CI:

$targetDir = 'D:\janeasystems\PowerToys\x64\Release'
$referencedFileVersionsPerDll = @{}
$totalFailures = 0

Get-ChildItem $targetDir -Recurse -Filter *.deps.json -Exclude UITests-FancyZones*,MouseJump.Common.UnitTests* | ForEach-Object {
    $depsJsonFullFileName = $_.FullName
    $depsJsonFileName = $_.Name
    $depsJson = Get-Content $depsJsonFullFileName | ConvertFrom-Json

    # We're doing a breadth first search to look for every runtime object.
    $iterateThroughEveryField = New-Object System.Collections.Generic.Queue[System.Object]
    $iterateThroughEveryField.Enqueue($depsJson)

    while($iterateThroughEveryField.Count -gt 0)
    {
        $currentObject = $iterateThroughEveryField.Dequeue();
        $currentObject.PSObject.Properties | ForEach-Object {
            if($_.Name -ne 'SyncRoot') {
                # Skip SyncRoot to avoid looping in array objects.
                # Care only about objects, not value types.
                $iterateThroughEveryField.Enqueue($_.Value)
                if($_.Name -eq 'runtime')
                {
                    # Cycle through each dll.
                    $_.Value.PSObject.Properties | ForEach-Object {
                        if($_.Name.EndsWith('.dll')) {
                            $dllName = Split-Path $_.Name -leaf
                            if([bool]($_.Value.PSObject.Properties.name -match 'fileVersion')) {
                                $dllFileVersion = $_.Value.fileVersion
                                if ([string]::IsNullOrEmpty($dllFileVersion) -and $dllName.StartsWith('PowerToys.'))` {
                                    # After VS 17.11 update some of PowerToys dlls have no fileVersion in deps.json even though the 
                                    # version is correctly set. This is a workaround to skip our dlls as we are confident that all of
                                    # our dlls share the same version across the dependencies.
                                    continue
                                }                                
                                # Add the entry to the dictionary of dictionary of lists
                                if(-Not $referencedFileVersionsPerDll.ContainsKey($dllName)) {
                                    $referencedFileVersionsPerDll[$dllName] = @{ $dllFileVersion = New-Object System.Collections.Generic.List[System.String] }
                                } elseif(-Not $referencedFileVersionsPerDll[$dllName].ContainsKey($dllFileVersion)) {
                                    $referencedFileVersionsPerDll[$dllName][$dllFileVersion] = New-Object System.Collections.Generic.List[System.String]
                                }
                                $referencedFileVersionsPerDll[$dllName][$dllFileVersion].Add($depsJsonFileName)
                            }
                        }
                    }
                }
            }
        }
    }
}
$referencedFileVersionsPerDll.keys | ForEach-Object {
    if($referencedFileVersionsPerDll[$_].Count -gt 1) {
		$dllName = $_
		Write-Host $dllName
		$referencedFileVersionsPerDll[$dllName].keys | ForEach-Object {
			Write-Host "`t" $_ 
			$referencedFileVersionsPerDll[$dllName][$_] | ForEach-Object {
				Write-Host "`t`t" $_
			}
		}
        $totalFailures++;
    }
}

@moooyo
Copy link
Contributor Author

moooyo commented Dec 20, 2024

@jaimecbernardo Ohh, you are right. That's no benefit to solve this problem. Let me add them back first.

@moooyo
Copy link
Contributor Author

moooyo commented Dec 20, 2024

/azp run

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@moooyo
Copy link
Contributor Author

moooyo commented Dec 20, 2024

Anyway, we can continue to move on this PR. Could you please review again.

In addition, I saw you mentioned that currently, WinUI3 app use their own dlls not the root folder one.

Now, we can not enable AOT in the WinUI3 modules. That's a trick thing I guess. Maybe we can change the check script to do the independent check for root folder and WinUI3 folder?

I'm not sure my understanding is true. Any insight? @jaimecbernardo

@jaimecbernardo
Copy link
Collaborator

@moooyo , That might be something that works, but I do wonder I then some dlls that are conflicting won't be verified because of "dependencies of dependencies" which deps.json file would be verified in root but not in WinUI3Apps folders 🤔 Something that can be tried out, I guess, but it must be verified that we still get a good check within WinUI3Apps folder.
Something that might be tried as well can be to understand what the WinUI3Apps need from Common.UI, make that a different project and try to see if conflicts are still detected if WinUI3 apps don't depend on WinForms and WPF.

@moooyo
Copy link
Contributor Author

moooyo commented Dec 20, 2024

That might be something that works, but I do wonder I then some dlls that are conflicting won't be verified because of "dependencies of dependencies" which deps.json file would be verified in root but not in WinUI3Apps folders 🤔 Something that can be tried out, I guess, but it must be verified that we still get a good check within WinUI3Apps folder.
Something that might be tried as well can be to understand what the WinUI3Apps need from Common.UI, make that a different project and try to see if conflicts are still detected if WinUI3 apps don't depend on WinForms and WPF.

Sure, Thanks! I will continue to investigate. Anyway, we can continue to move on this PR without the change of remove UseWPF and UseWindowsForms.

@moooyo moooyo changed the title [AOT] Remove UseWPF and UseWindowsForms in Common.UI and clean up AOT build issue [AOT] Clean up AOT build issue in Common.UI Dec 20, 2024
@moooyo moooyo added the Needs-Review This Pull Request awaits the review of a maintainer. label Dec 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs-Review This Pull Request awaits the review of a maintainer.
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

5 participants