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

Improve debugging experience: add global switch MSBuildDebugEngine; Inject binary logger from BuildManager; print static graph as .dot file #6639

Merged
merged 19 commits into from
Jul 26, 2021

Conversation

cdmihai
Copy link
Contributor

@cdmihai cdmihai commented Jun 29, 2021

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

  • add one env var, MSBuildDebugEngine to turn everything on and also automatically pick ./MSBuild_Logs as the debug log path for everything that spews out log files.
  • in addition, when MSBuildDebugEngine:
    • inject a binary logger directly from the build manager. This is super useful when running MSBuild in VS, as the build logging that VS gives is kind of lacking
    • dump a .dot representation of the static graph, when one is available

This is how MSBuild_Logs looks like after doing a build (both VS and cmdline should produce this):
image

@cdmihai cdmihai changed the title Debugging improvements Improve debugging experience Jun 29, 2021
Copy link
Member

@Forgind Forgind left a 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()),
Copy link
Member

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()?

Copy link
Contributor Author

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.

Copy link
Member

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

Suggested change
(_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 ||
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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/Build/BackEnd/BuildManager/BuildManager.cs Outdated Show resolved Hide resolved
src/Shared/Traits.cs Outdated Show resolved Hide resolved
}
});

public static string ProcessInfoString
Copy link
Member

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.

Copy link
Contributor Author

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.

src/Shared/Traits.cs Outdated Show resolved Hide resolved
src/Build/BackEnd/Components/Scheduler/Scheduler.cs Outdated Show resolved Hide resolved
@@ -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
Copy link
Contributor Author

@cdmihai cdmihai Jul 13, 2021

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).

Copy link
Member

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.

Copy link
Contributor Author

@cdmihai cdmihai Jul 14, 2021

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.

Copy link
Member

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.

Copy link
Member

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.

@benvillalobos
Copy link
Member

Can you add this PR to the changewaves doc?

src/Shared/Debugging/DebugUtils.cs Outdated Show resolved Hide resolved
@@ -486,7 +488,7 @@ public void BeginBuild(BuildParameters parameters)
ILoggingService InitializeLoggingService()
{
ILoggingService loggingService = CreateLoggingService(
_buildParameters.Loggers,
(_buildParameters.Loggers ?? Enumerable.Empty<ILogger>()).Concat(GetDebuggingLoggers()),
Copy link
Member

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

Suggested change
(_buildParameters.Loggers ?? Enumerable.Empty<ILogger>()).Concat(GetDebuggingLoggers()),
AppendDebuggingLoggers(_buildParameters.Loggers),

so we have neither linq nor the empty enumerables in the main path.

Comment on lines 523 to 524
// VS builds discard many msbuild events so attach a binlogger to capture them all.
IEnumerable<ILogger> GetDebuggingLoggers()
Copy link
Member

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

Copy link
Member

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?

Copy link
Member

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.

src/Build/BackEnd/BuildManager/BuildManager.cs Outdated Show resolved Hide resolved
@@ -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
Copy link
Member

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.

@cdmihai cdmihai changed the title Improve debugging experience Improve debugging experience: add global switch MSBuildDebugEngine; Inject binary logger from BuildManager; print static graph as .dot file Jul 23, 2021
@Forgind Forgind added the merge-when-branch-open PRs that are approved, except that there is a problem that means we are not merging stuff right now. label Jul 26, 2021
@ladipro ladipro merged commit cdb5077 into dotnet:main Jul 26, 2021
static DebugUtils()
{
string environmentDebugPath = Environment.GetEnvironmentVariable("MSBUILDDEBUGPATH");
var debugDirectory = environmentDebugPath ?? Path.Combine(Directory.GetCurrentDirectory(), "MSBuild_Logs");

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?

Copy link
Contributor Author

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 cdmihai deleted the improveDebugging branch August 19, 2021 17:39
@davkean
Copy link
Member

davkean commented Aug 26, 2021

@cdmihai This appears to have regressed VS: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1386501

@rainersigwald
Copy link
Member

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.

rainersigwald added a commit to rainersigwald/msbuild that referenced this pull request Aug 26, 2021
…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
rainersigwald pushed a commit that referenced this pull request Aug 30, 2021
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
merge-when-branch-open PRs that are approved, except that there is a problem that means we are not merging stuff right now.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants