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

Make Avalonia UI the default on all platforms and move in-process #1207

Merged
merged 37 commits into from
Apr 24, 2023

Conversation

mjcheetham
Copy link
Collaborator

@mjcheetham mjcheetham commented Apr 18, 2023

Today GCM has an architecture that delegates all GUI prompting to (bundled) external executables, using WPF-based helpers on Windows and Avalonia UI helpers on Mac and Linux. Each host provider has its own GUI helper executable, meaning we currently bundle four (GitHub.UI, GitLab.UI, Atlassian.Bitbucket.UI, git-credential-manager.ui [for generic auth]) helpers in addition to the core git-credential-manager CLI executable.

On Linux we use the "publish as a single-file" model for each of these executables, meaning we have a copy of the CLR included for each executable! That means five CLRs!

Now that the Avalonia UI-based GUI helpers have matured, we can look at dropping the WPF helpers and reduce the effort required to ship new GUI by 50% (we no longer need to do everything twice: once with WPF and once with Avalonia UI).

In addition, in a recent spike, I found that it may be possible to bring the Avalonia UI helpers in-process, with nearly zero net-new code. This would help reduce our installation footprint on Linux down to approximately 1/4 of the current size. The single executable model would also help decrease build times, and increase end-user performance since we no longer pay the process startup costs using external processes!

We need to be mindful that this change may result in a small increase in the Windows installation as we now bring in extra libraries used to support Avalonia UI, whereas previously we relied on WPF libraries as included with Windows 'for free'. This is expected to be minimal.

As for the current GUI extensibility model, we are not aware of any consumers, but it would not be much effort to keep the existing external process model, but now just default to using our in-process GUI options in the absence of external configuration. This still gives tools like VS or VSCode options to integrate native UI for GCM in the future.

Initially, we are still keeping the WPF helpers around on Windows but disabled by default.
We can drop these after at least one release using the in-proc Avalonia helpers.

To switch back to the WPF helpers, use credential.devUseLegacyUIHelpers or GCM_DEV_USELEGACYUIHELPERS.

@mjcheetham mjcheetham requested a review from ldennington April 18, 2023 21:49
@mjcheetham mjcheetham force-pushed the inproc branch 2 times, most recently from eb36ba7 to cc6d0c3 Compare April 18, 2023 21:56
@mjcheetham mjcheetham force-pushed the inproc branch 2 times, most recently from c436534 to 4230893 Compare April 18, 2023 23:05
@mjcheetham mjcheetham self-assigned this Apr 19, 2023
felo-man

This comment was marked as abuse.

Rename the WPF implementation of DialogWindow to prevent name collisions
that would otherwise occur in upcoming commits where we merge the
Avalonia implementation of DialogWindow into the Core project.
Explicitly target the WPF Dispatcher type using the fully qualified type
name to prevent an ambiguous reference that would otherwise occur in
upcomming commits where the Avalonia Dispatcher, and the GCM Dispatcher
are merged in to the Core project.
Add the .Windows suffix to the WPF project namespaces to prevent name
collisions that would otherwise occur in future commits where the
Avalonia UI types are to be merged in to the core projects.

Also clean up some unused XML namespace imports.
Set up a message pump on the main thread in the primary GCM executable
in preparation to adding in-process, AvaloniaUI-based UI.

Also move to explicitly shutdown the dispatcher correctly rather than
just killing the process.
Default to using the in-proc Avalonia GUI on all platforms, with a
fallback flag that can be set on Windows to continue to use the legacy
WPF, out-of-proc UI helpers.
@mjcheetham mjcheetham changed the title Drop WPF UI helpers and move all UI in-process Make Avalonia UI the default on all platforms and move in-process Apr 21, 2023
@mjcheetham mjcheetham added the gui Specific to graphical user interface controls label Apr 21, 2023
@mjcheetham mjcheetham marked this pull request as ready for review April 21, 2023 17:58
Copy link
Contributor

@ldennington ldennington left a comment

Choose a reason for hiding this comment

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

This is so exciting! Great work, Matthew! 🚀

@@ -81,15 +80,6 @@ $DOTNET_ROOT/dotnet publish "$GCM_SRC" \
-p:PublishSingleFile=true \
--output="$(make_absolute "$PAYLOAD")" || exit 1

echo "Publishing core UI helper..."
Copy link
Contributor

Choose a reason for hiding this comment

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

Yay! So happy these are going away.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Builds are much faster and will be easier to maintain or modify going forwards (at least once we can drop the WPF helpers entirely).

// Default to the 'first' choice in the menu
TerminalMenuItem choice = menu.Show(0);

if (choice == browserItem) goto case OAuthAuthenticationModes.Browser;
Copy link
Contributor

Choose a reason for hiding this comment

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

I know this was already here, but just wanted to call out that I think it's elegant - one of the cases where a goto does make sense.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, the C# goto case keywords used together is a neat way to achieve this sort of control flow, without allowing for spaghetti jumps (can only jump to another case).

var promptCts = new CancellationTokenSource();
var tokenCts = new CancellationTokenSource();

// Show the dialog with the device code but don't await its closure
Task promptTask = InvokeHelperAsync(command, promptArgs.ToString(), null, promptCts.Token);
Task promptTask = ShowDeviceCodeViaHelperAsync(dcr, command, args, promptCts.Token);
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice - the refactors you've done here make the sequence of events a lot clearer imo.

src/shared/GitLab/GitLabAuthentication.cs Outdated Show resolved Hide resolved
@@ -102,6 +102,7 @@ public static class EnvironmentVariables
public const string OAuthDeviceEndpoint = "GCM_OAUTH_DEVICE_ENDPOINT";
public const string OAuthClientAuthHeader = "GCM_OAUTH_USE_CLIENT_AUTH_HEADER";
public const string OAuthDefaultUserName = "GCM_OAUTH_DEFAULT_USERNAME";
public const string GcmDevUseLegacyUiHelpers = "GCM_DEV_USELEGACYUIHELPERS";
Copy link
Contributor

Choose a reason for hiding this comment

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

Why the use of DEV in the name here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This isn't something we want to necessarily advertise or be something that users can rely on being there for the long term. It's a 'hidden' setting – we don't document it.

src/shared/GitLab/GitLabAuthentication.cs Outdated Show resolved Hide resolved
@@ -21,12 +21,13 @@
<PackageReference Include="Microsoft.Identity.Client" Version="4.52.0" />
<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" Version="2.28.0" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.21216.1" />
<PackageReference Include="Avalonia" Version="0.10.18" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.18" />
<PackageReference Include="Avalonia" Version="11.0.0-preview6" />
Copy link
Contributor

Choose a reason for hiding this comment

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

Wow! Big jump.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah they moved from 0.10 to dropping the 0. prefix with v11

public bool ShowCustomWindowBorder
{
// Draw the window border explicitly on Windows
get => ShowCustomChrome && PlatformUtils.IsWindows();
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not quite understanding the Custom Chrome concept. Any docs I can take a look at to learn more?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The "client area" is the part of a window that we can draw into. Typically this is the box 'inside' of the window titlebar, border and controls that the OS provides.

When we "extend" the client area, we can now draw over the entire window surface, including the title bar and window controls. Note that on macOS the traffic-light controls are still always drawn, but on Windows we are left without any "X" control.

The last column shows the custom window chrome and controls we draw. Here we actually explicitly disable the traffic-light controls on macOS.

image

The preference on macOS is the middle option, and on Windows it's the right-most one.

Copy link
Collaborator Author

@mjcheetham mjcheetham Apr 24, 2023

Choose a reason for hiding this comment

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

Oh and on macOS the thin 1-pixel window border is always drawn, but on Windows it's not.
This means on Windows we want to manually draw a 1-px border or else our window can blend in to the background.

image

image

Simplify where we check for user interaction and/or terminal prompts
during authentication, to only need to check at the start of each
`GetAuthenticationAsync` call.

Note that is is a behaviour change - we now require user interaction to
be allowed to perform authentication even with only 1 mode available.
This is technically more 'correct' because when the user disables
interactivity, we should never require interaction to continue. Although
we are not asking the user to select a mode of authentication, we still
either need them to enter a username/password, or respond to a browser
OAuth flow.
Support dark theme resource dictionaries.
To keep the size of the Windows distribution small, directly target the
Win32 Avalonia packages rather than the Desktop package that includes
macOS and Linux dependencies.
@mjcheetham mjcheetham merged commit d6a4cf3 into git-ecosystem:main Apr 24, 2023
@mjcheetham mjcheetham deleted the inproc branch April 24, 2023 23:45
mjcheetham added a commit that referenced this pull request May 2, 2023
**Changes:**

- Support ports in URL-scoped config (#825)
- Support URL-scoped enterprise default settings (#1149)
- Add support for client TLS certificates (#1152)
- Add TRACE2 support(#1131, #1151, #1156, #1162)
- Better browser detection inside of WSL (#1148)
- Handle expired OAuth refresh token for generic auth (#1196)
- Target *-latest runner images in CI workflow (#1178)
- Various bug fixes:
  - Ensure we create a WindowsProcessManager on Windows (#1146)
  - Ensure we start child processes created with ProcessManager (#1177)
  - Fix app path name of Windows dropping file extension (#1181)
  - Ensure we init IEnvironment before SessionManager (#1167)
  - git: consistently read from stdout before exit wait (#1136)
  - trace2: guard against null pipe client in dispose (#1135)
- Make Avalonia UI the default Windows and move to in-process (#1207)
- Add Git configuration options for trace & debug (#1228)
- Transition from Nerdbank.GitVersioning to a version file (#1231)
- Add support for using the current Windows user for WAM on DevBox
(#1197)
- Various documentation updates:
  - org-rename: update references to GitCredentialManager (#1141)
  - issue templates: remove core suffix (#1180)
  - readme: add link to project roadmap (#1204)
  - docs: add bitbucket app password requirements (#1213)
  - .net tool: clarify install instructions (#1126)
  - docs: call out different GCM install paths in WSL docs (#1168)
  - docs: add trace2 to config/env documentation (#1230)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
gui Specific to graphical user interface controls
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants