-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Improve debugging experience: add global switch MSBuildDebugEngine; Inject binary logger from BuildManager; print static graph as .dot file #6639
Conversation
ed05a8b
to
45f2eb3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this looks good and helpful 🙂
@@ -486,7 +488,7 @@ public void BeginBuild(BuildParameters parameters) | |||
ILoggingService InitializeLoggingService() | |||
{ | |||
ILoggingService loggingService = CreateLoggingService( | |||
_buildParameters.Loggers, | |||
(_buildParameters.Loggers ?? Enumerable.Empty<ILogger>()).Concat(GetDebuggingLoggers()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you need to concat something to empty as opposed to just taking GetDebuggingLoggers()?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If buildParam.Loggers is not null, then I need to concat with the method. If it is null, I need to take just the method. This was the shortest way I found to express it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is very concise but since it's just for us, I'd rather see something like
(_buildParameters.Loggers ?? Enumerable.Empty<ILogger>()).Concat(GetDebuggingLoggers()), | |
AppendDebuggingLoggers(_buildParameters.Loggers), |
so we have neither linq nor the empty enumerables in the main path.
// VS builds discard many msbuild events so attach a binlogger to capture them all. | ||
IEnumerable<ILogger> GetDebuggingLoggers() | ||
{ | ||
if (CurrentProcessMatchesDebugName() is false || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you need to say is false
? Aren't there non-nullable bools?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just style preference, has nothing to do with nullability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer !CurrentProcessMatchesDebugName, but that's fine. This is probably more readable, to be honest.
src/Shared/Debugging/DebugUtils.cs
Outdated
} | ||
}); | ||
|
||
public static string ProcessInfoString |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this have to be =>
rather than =
? I don't see the difference here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, everything in the expression should be constant throughout process lifetime.
f406bb4
to
fe59e40
Compare
@@ -41,7 +45,15 @@ static ExceptionHandling() | |||
/// <returns></returns> | |||
private static string GetDebugDumpPath() | |||
{ | |||
string debugPath = Environment.GetEnvironmentVariable("MSBUILDDEBUGPATH"); | |||
string debugPath = | |||
#if CLR2COMPATIBILITY || MICROSOFT_BUILD_ENGINE_OM_UNITTESTS |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Forgind
Made this change after you signed off. ChangeWaves are not visible to the MICROSOFT_BUILD_ENGINE_OM_UNITTESTS assembly so I had to ifdef it out. The alternative is to move ChangeWaves from Microsoft.Build.Framework into shared sources, but that seems like a bigger change than this one (and it's probably good to refrain from increasing shared sources).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe move the test to Engine.UnitTests? It kinda makes sense that Engine.OM.UnitTests wouldn't be able to see Framework, but I'm not a huge fan of ifdef'ing based on being in a test—to me, it feels like making tests not actually test code, just test similar code. I'll let @benvillalobos opine on the right way to handle ChangeWaves visibility, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The entire test assembly ends up depending on ChangeWaves, not just a few tests, so it's not feasible to move tests. Only other solution that I see is to move ChangeWaves into the shared sources directory.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I remember having issues in trying to make changewaves shared. Lots of "This type exists multiple times" issues. If you can move it with no issues then by all means. It's unrelated to what this PR is doing so I'd say make an issue for it but don't let it block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a comment and this is fine by me.
Can you add this PR to the changewaves doc? |
@@ -486,7 +488,7 @@ public void BeginBuild(BuildParameters parameters) | |||
ILoggingService InitializeLoggingService() | |||
{ | |||
ILoggingService loggingService = CreateLoggingService( | |||
_buildParameters.Loggers, | |||
(_buildParameters.Loggers ?? Enumerable.Empty<ILogger>()).Concat(GetDebuggingLoggers()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is very concise but since it's just for us, I'd rather see something like
(_buildParameters.Loggers ?? Enumerable.Empty<ILogger>()).Concat(GetDebuggingLoggers()), | |
AppendDebuggingLoggers(_buildParameters.Loggers), |
so we have neither linq nor the empty enumerables in the main path.
// VS builds discard many msbuild events so attach a binlogger to capture them all. | ||
IEnumerable<ILogger> GetDebuggingLoggers() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cc @drewnoakes on this since it interacts with project-system-tools
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rainersigwald do you have any expectation of consequence here, or is this just an FYI?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just an FYI. We should happily handle having multiple loggers attached and everything should continue working as it is, but these logs might be helpful in some cases.
@@ -41,7 +45,15 @@ static ExceptionHandling() | |||
/// <returns></returns> | |||
private static string GetDebugDumpPath() | |||
{ | |||
string debugPath = Environment.GetEnvironmentVariable("MSBUILDDEBUGPATH"); | |||
string debugPath = | |||
#if CLR2COMPATIBILITY || MICROSOFT_BUILD_ENGINE_OM_UNITTESTS |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a comment and this is fine by me.
915f4b3
to
4e39ec5
Compare
4e39ec5
to
aa5d749
Compare
static DebugUtils() | ||
{ | ||
string environmentDebugPath = Environment.GetEnvironmentVariable("MSBUILDDEBUGPATH"); | ||
var debugDirectory = environmentDebugPath ?? Path.Combine(Directory.GetCurrentDirectory(), "MSBuild_Logs"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rainersigwald @cdmihai I encountered the following unhandled exception when this code was executed from within our service hub process where we invoke MSBulild programmatically -
I had set the MSBuildDebugEngine
environment variable (and we have our own BinaryLogger
specified programmatically. But I had not set MSBuildDebugPath
.
System.UnauthorizedAccessException: Access to the path 'C:\Program Files\Microsoft Visual Studio\2022\Main\Common7\ServiceHub\Hosts\ServiceHub.Host.CLR.AnyCPU\MSBuild_Logs' is denied. HResult='-2147024891'
at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.Directory.InternalCreateDirectory(String fullPath, String path, Object dirSecurityObj, Boolean checkHost)
at System.IO.Directory.InternalCreateDirectoryHelper(String path, Boolean checkHost)
at Microsoft.Build.Shared.FileUtilities.EnsureDirectoryExists(String directoryPath)
at Microsoft.Build.Shared.Debugging.DebugUtils..cctor()
---
System.TypeInitializationException: The type initializer for 'Microsoft.Build.Shared.Debugging.DebugUtils' threw an exception. HResult='-2146233036'
at Microsoft.Build.Internal.CommunicationsUtilities.GetEnvironmentVariables()
at Microsoft.Build.Internal.Utilities.GetEnvironmentProperties()
at Microsoft.Build.Evaluation.ProjectCollection.get_EnvironmentProperties()
at Microsoft.Build.Evaluation.ProjectCollection.InitializeToolsetCollection(ToolsetRegistryReader registryReader, ToolsetConfigurationReader configReader)
at Microsoft.Build.Evaluation.ProjectCollection..ctor(IDictionary`2 globalProperties, IEnumerable`1 loggers, IEnumerable`1 remoteLoggers, ToolsetDefinitionLocations toolsetDefinitionLocations, Int32 maxNodeCount, Boolean onlyLogCriticalEvents, Boolean loadProjectsReadOnly)
at Microsoft.Build.Evaluation.ProjectCollection.get_GlobalProjectCollection()
Not a huge issue but now that I know about MSBuildDebugPath
:) But have you considered moving the default location to something like %temp%
at least for programmatic invocations so that it is harder to run into problems like above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Temp dir makes it a worse experience when debugging, as we're usually cd-ed into some repo directory when setting MSBuildDebugEngine and it's nice to just get all the logs there. Rather than forcing everybody to look into tmp, or forcing everybody to set MSBuildDebugPath, maybe the option that maximizes global experience is to only have users that do not propagate the user's CWD set MSBuildDebugPath.
That being said, wouldn't it be "more correct" to propagate the user's CWD to the service hub process?
@cdmihai This appears to have regressed VS: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1386501 |
Let's go ahead and revert this until we can chase the problems down. I think the only thing that really needs to change is the moved default emergency-stack-dump-text-file location. |
…ngine; Inject binary logger from BuildManager; print static graph as .dot file (dotnet#6639)" This reverts commit cdb5077. Conflicts: documentation/wiki/ChangeWaves.md src/Build/BackEnd/Components/Scheduler/Scheduler.cs src/Shared/Traits.cs
In #6639 I thought it would be a good idea to always expose exceptions by always setting msbuild's debug path, fallbacking to the current working directory. But that's a breaking change. Also, some processes (like VS helpers) do not propagate the user's CWD and thus end up writing under Program Files which fails with permission exceptions. So only set the debug path to the current working directory when `MSBuildDebugEngine` is set.
Context
There's still a bit of friction when debugging MSBuild, especially under VS. You have to set multiple env vars, or search through temp, find all the obscure env var names, etc
Changes Made
MSBuildDebugEngine
to turn everything on and also automatically pick./MSBuild_Logs
as the debug log path for everything that spews out log files.MSBuildDebugEngine
:.dot
representation of the static graph, when one is availableThis is how
MSBuild_Logs
looks like after doing a build (both VS and cmdline should produce this):