Fix build race condition in VS templates#6413
Conversation
…r a number of manual tests, works with single-project C# and C#/C++ wapproj (not C# unit test)
…nd start after package installation
…arted and the Unadvise call was premature
…ngs (resolves in one click instead of two)
guimafelipe
left a comment
There was a problem hiding this comment.
Overall, it was a bit harder to track what the build guard is doing. There were a group of booleans that were always changed and used to check what to do. I would advise to change the design a bit to be a proper state machine, with clear transitions between the state instead of tracking those 6 boolean values. What do you think?
| public void SetReleaseCondition(Func<bool> condition) | ||
| { | ||
| _shouldRelease = condition; | ||
| } | ||
|
|
||
| public void SetInfoBarMessage(string message) | ||
| { | ||
| ThreadHelper.ThrowIfNotOnUIThread(); | ||
| _infoBarMessage = message; | ||
| if (_isBlocking) | ||
| { | ||
| ShowInfoBarWhenShellReady(); | ||
| } | ||
| } |
There was a problem hiding this comment.
Could we use the C# setter/getter here for clarity?
There was a problem hiding this comment.
While it's theoretically possible, I'm not sure it makes sense for SetInfoBarMessage because it calls a UI change ShowInfoBarWhenShellIsReady() as well as assigning the _message value.
There was a problem hiding this comment.
I'd add that a 'write-only' property is unorthodox - better a set method
| return; | ||
| } | ||
|
|
||
| _solutionBuildManager5 = ServiceProvider.GlobalProvider.GetService(typeof(SVsSolutionBuildManager)) as IVsSolutionBuildManager5; |
There was a problem hiding this comment.
Do we need to get it every time? Can we just set this up in the constructor once?
| } | ||
|
|
||
| _solutionBuildManager5 = ServiceProvider.GlobalProvider.GetService(typeof(SVsSolutionBuildManager)) as IVsSolutionBuildManager5; | ||
| if (_solutionBuildManager5 != null) |
There was a problem hiding this comment.
if it's not available, we just fall back to the race condition? probably fine, but a comment would help
| if (_shell == null) | ||
| { | ||
| _shell = ServiceProvider.GlobalProvider.GetService(typeof(SVsShell)) as IVsShell; | ||
| } | ||
|
|
||
| if (_shell == null) | ||
| { | ||
| return; | ||
| } |
There was a problem hiding this comment.
good use of an internal property, Shell
| _solutionBuildManager5 = ServiceProvider.GlobalProvider.GetService(typeof(SVsSolutionBuildManager)) as IVsSolutionBuildManager5; | ||
| if (_solutionBuildManager5 != null) | ||
| { | ||
| _solutionBuildManager5.AdviseUpdateSolutionEvents4(this, out _adviseCookie4); |
There was a problem hiding this comment.
not checking return for error (here and other places)
| // Shell not yet initialized; defer until it is | ||
| if (!_isShellPropertyAdvised) | ||
| { | ||
| _shell.AdviseShellPropertyChanges(this, out _shellPropertyCookie); |
| s_installationComplete = false; | ||
| } | ||
|
|
||
| s_buildGuard.SetReleaseCondition(() => s_installationComplete); |
There was a problem hiding this comment.
referencing s_buildGuard here without condition, while its assignment was conditional
| s_buildGuard.SetInfoBarMessage(string.Format(Resources._1055, projectName, packageNames)); | ||
| } | ||
|
|
||
| if (!_packageRestoreEnabled && _nuGetPackages != null && _nuGetPackages.Any()) |
There was a problem hiding this comment.
AI-generated code isn't always very compact - can just reuse condition on L133
| @@ -96,36 +166,55 @@ await ThreadHelper.JoinableTaskFactory.RunAsync(async () => | |||
| await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); | |||
| int canceled = 0; // Initialize as not canceled | |||
There was a problem hiding this comment.
move closer to call site, L183
| { | ||
| } | ||
|
|
||
| public void Dispose() |
There was a problem hiding this comment.
this doesn't appear to be implementing the dispose pattern - e.g., there are no unmanaged resources being released. I'd change the name to something else to avoid confusion.
|
|
||
| // NuGet package restore is enabled by default, so only return false if we can confirm it is disabled in | ||
| // the user's config. | ||
| private static bool IsNuGetPackageRestoreEnabled() |
There was a problem hiding this comment.
should also consult the global override env var EnableNuGetPackageRestore:
https://learn.microsoft.com/en-us/nuget/reference/nuget-config-file#packagerestore-section
| public int OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded) | ||
| { | ||
| return VSConstants.S_OK; | ||
| } |
There was a problem hiding this comment.
use expression-bodied for compact one-liners
| public int OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded) | |
| { | |
| return VSConstants.S_OK; | |
| } | |
| public int OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded) => VSConstants.S_OK; |
Before
If a user attempts to build the C# templates before the NuGet wizard installs package dependencies, the build would fail with Xaml compile errors
With this PR
Validation
Manual Validation
A microsoft employee must use /azp run to validate using the pipelines below.
WARNING:
Comments made by azure-pipelines bot maybe inaccurate.
Please see pipeline link to verify that the build is being ran.
For status checks on the main branch, please use TransportPackage-Foundation-PR
(https://microsoft.visualstudio.com/ProjectReunion/_build?definitionId=81063&_a=summary)
and run the build against your PR branch with the default parameters.