phmonte / Buildalyzer

A utility to perform design-time builds of .NET projects without having to think too hard about it.
MIT License
589 stars 92 forks source link

Handle WPF Custom Control Libraries #178

Closed markrendle closed 3 years ago

markrendle commented 3 years ago

There's something weird with .NET 4.x WPF Custom Control libraries, the build process seems to generate a temp project and it breaks the Analyzer.

Specifically, in the EventProcessor code below, the ProjectStarted method pushes a null onto the _currentResult stack if the project file path doesn't match OR if there's no tfm. ProjectFinished doesn't check the project file path so it's popping nulls for either project file mismatches or empty tfms. This results in the AnalyzerResult showing as unsuccessful.

private void ProjectStarted(object sender, ProjectStartedEventArgs e)
{
    // If we're not using an analyzer (I.e., from a binary log) and this is the first project file path we've seen, then it's the primary
    if (_projectFilePath == null)
    {
        _projectFilePath = AnalyzerManager.NormalizePath(e.ProjectFile);
    }

    // Make sure this is the same project, nested MSBuild tasks may have spawned additional builds of other projects
    if (AnalyzerManager.NormalizePath(e.ProjectFile) == _projectFilePath)
    {
        // Get the TFM for this project
        string tfm = e.Properties?.Cast<DictionaryEntry>()
            .FirstOrDefault(x => string.Equals(x.Key.ToString(), "TargetFrameworkMoniker", StringComparison.OrdinalIgnoreCase)).Value as string;
        if (!string.IsNullOrWhiteSpace(tfm))
        {
            if (!_results.TryGetValue(tfm, out AnalyzerResult result))
            {
                result = new AnalyzerResult(_projectFilePath, _manager, _analyzer);
                _results[tfm] = result;
            }
            result.ProcessProject(e);
            _currentResult.Push(result);
            return;
        }
    }

    // Push a null result so the stack is balanced on project finish
    // _currentResult.Push(null);
}

private void ProjectFinished(object sender, ProjectFinishedEventArgs e)
{
    AnalyzerResult result = _currentResult.Pop();
    if (result != null)
    {
        result.Succeeded = e.Succeeded;
    }
}

This PR moves the _currentResult.Push(null) inside the project file matching if block and adds the project file check to ProjectFinished as well, which I think should keep the _currentResult Stack balanced.

daveaglick commented 3 years ago

Thanks! And thanks for the review @psfinaki. I've pulled this in and will do a little more testing before publishing a release. Look for a new version to hit NuGet shortly.