msbuildcruisecontrol.net

MSBuild: How to obtain number of warnings raised?


There is a MSBuild script, that includes number if Delphi and C# projects, unit tests etc.

The problem is: how to mark build failed if warnings were raised (for testing purposes, not for release builds)? Using LogError instead of LogWarning in custom tasks seems to be not a good option, because the build should test as much as it's able (until real error) to report as much warnings as possible in a time (build project is using in CruiseControl.NET).

May be, the solution is to create my own logger that would store warnings flag inside, but I cannot find if there is a way to read this flag in the end of build?

P.S. There is no problem to fail the build immediately after receiving a warning (Delphi compiler output is processed by custom task, and /warnaserror could be used for C#), but the desired behavior is "build everything; collect all warnings; fail the build" to report about all warnings, not only about the first one.

P.P.S. As far as I really need not number of warnings, but just flag of their presence, I decided to simplify signaling mechanism, and use trivial Mutex instead of shared memory. Code is below:

using System;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using System.Threading;

namespace Intrahealth.Build.WarningLogger
{
    public sealed class WarningLoggerCheck : Task
    {
        public override bool Execute()
        {
            Log.LogMessage("WarningLoggerCheck:" + mutexName + "...");
            result = false;
            Mutex m = null;
            try
            {
                m = Mutex.OpenExisting(mutexName);
            }
            catch (WaitHandleCannotBeOpenedException)
            {
                result = true;
            }
            catch (Exception)
            {
            }

            if (result)
                Log.LogMessage("WarningLoggerCheck PASSED");
            else
                Log.LogError("Build log contains warnings. Build is FAILED");

            return result;
        }

        private bool result = true;
        [Output]
        public bool Result
        {
            get { return result; }
        }

        private string mutexName = "WarningLoggerMutex";
        public string MutexName
        {
            get { return mutexName; }
            set { mutexName = value ?? "WarningLoggerMutex"; }
        }
    }

    public class WarningLogger : Logger
    {
        internal static int warningsCount = 0;
        private string mutexName = String.Empty;
        private Mutex mutex = null;

        public override void Initialize(IEventSource eventSource)
        {
            eventSource.WarningRaised += new BuildWarningEventHandler(eventSource_WarningRaised);
        }

        private void SetMutex()
        {
            if (mutexName == String.Empty)
            {
                mutexName = "WarningLoggerMutex";
                if (this.Parameters != null && this.Parameters != String.Empty)
                {
                    mutexName = this.Parameters;
                }
            }

            mutex = new Mutex(false, mutexName);
        }

        void eventSource_WarningRaised(object sender, BuildWarningEventArgs e)
        {
            if (e.Message != null && e.Message.Contains("MSB3146"))
                return;
            if (e.Code != null && e.Code.Equals("MSB3146"))
                return;

            if (warningsCount == 0)
                SetMutex();
            warningsCount++;
        }
    }
}

Solution

  • AFAIK MSBuild has no built-in support to retrieve the warning count at a given point of the build script. You can however follow these steps to achieve this goal:

    1. Create a custom logger that listens for the warning event and counts the number of warnings
    2. Create a custom task that exposes an [Output] WarningCount property
    3. The custom task gets somehow the value of the warning count from the custom logger

    The most difficult step is step 3. For this there are several options and you can freely search them under IPC - Inter Process Comunication. Follows a working example of how you can achieve this. Each item is a different Class Library.

    SharedMemory

    http://weblogs.asp.net/rosherove/archive/2003/05/01/6295.aspx

    I've created a wrapper for named shared memory that was part of a larger project. It basically allows serialized types and object graphs to be stored in and retrieved from shared memory (including as you'd expect cross process). Whether the larger project ever gets completed is another matter ;-).

    SampleLogger

    Implements the custom logger that keeps track of the warning count.

    namespace SampleLogger
    {
        using System;
        using Microsoft.Build.Utilities;
        using Microsoft.Build.Framework;
        using DM.SharedMemory;
    
        public class MySimpleLogger : Logger
        {
            private Segment s;
            private int warningCount;
    
            public override void Initialize(IEventSource eventSource)
            {
                eventSource.WarningRaised += new BuildWarningEventHandler(eventSource_WarningRaised);
    
                this.s = new Segment("MSBuildMetadata", SharedMemoryCreationFlag.Create, 65535);
                this.s.SetData(this.warningCount.ToString());
            }
    
            void eventSource_WarningRaised(object sender, BuildWarningEventArgs e)
            {
                this.warningCount++;
                this.s.SetData(this.warningCount.ToString());
            }
    
            public override void Shutdown()
            {
                this.s.Dispose();
                base.Shutdown();
            }
        }
    }
    

    SampleTasks

    Implements the custom task that reads the number of warnings raised in the MSbuild project. The custom task reads from the shared memory written by the custom logger implemented in class library SampleLogger.

    namespace SampleTasks
    {
        using System;
        using Microsoft.Build.Utilities;
        using Microsoft.Build.Framework;
        using DM.SharedMemory;
    
        public class BuildMetadata : Task
        {
            public int warningCount;
    
            [Output]
            public int WarningCount
            {
                get
                {
                    Segment s = new Segment("MSBuildMetadata", SharedMemoryCreationFlag.Attach, 0);
                    int warningCount = Int32.Parse(s.GetData() as string);
                    return warningCount;
                }
            }
    
            public override bool Execute()
            {
                return true;
            }
        }
    }
    

    Going for a spin.

    <?xml version="1.0" encoding="UTF-8"?>
    <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Main">
        <UsingTask TaskName="BuildMetadata" AssemblyFile="F:\temp\SampleLogger\bin\debug\SampleTasks.dll" />
    
        <Target Name="Main">
            <Warning Text="Sample warning #1" />
            <Warning Text="Sample warning #2" />
    
            <BuildMetadata>
                <Output
                    TaskParameter="WarningCount"
                    PropertyName="WarningCount" />
            </BuildMetadata>
    
            <Error Text="A total of $(WarningCount) warning(s) were raised." Condition="$(WarningCount) > 0" />
        </Target>
    </Project>
    

    If you run the following command:

    c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\MSBuild test.xml /logger:SampleLogger.dll
    

    This will be the output:

    Microsoft (R) Build Engine Version 2.0.50727.3053
    [Microsoft .NET Framework, Version 2.0.50727.3053]
    Copyright (C) Microsoft Corporation 2005. All rights reserved.
    
    Build started 30-09-2008 13:04:39.
    __________________________________________________
    Project "F:\temp\SampleLogger\bin\debug\test.xml" (default targets):
    
    Target Main:
        F:\temp\SampleLogger\bin\debug\test.xml : warning : Sample warning #1
        F:\temp\SampleLogger\bin\debug\test.xml : warning : Sample warning #2
        F:\temp\SampleLogger\bin\debug\test.xml(15,3): error : A total of 2 warning(s) were raised.
    Done building target "Main" in project "test.xml" -- FAILED.
    
    Done building project "test.xml" -- FAILED.
    
    Build FAILED.
    F:\temp\SampleLogger\bin\debug\test.xml : warning : Sample warning #1
    F:\temp\SampleLogger\bin\debug\test.xml : warning : Sample warning #2
    F:\temp\SampleLogger\bin\debug\test.xml(15,3): error : A total of 2 warning(s) were raised.
        2 Warning(s)
        1 Error(s)
    
    Time Elapsed 00:00:00.01