NDepend Task for NAnt…

October 28, 2005

I was recently introduced to NDepend. While I can’t say that I fully understand all the analysis that it provides yet, I really like what I have grokked. Hopefully, as I learn more it will enable me to find my weak areas and improve my overall designs. I was a little disappointed to see that it looks like no work has been done on it as a system in over a year but I figured it was worth a little bit of my time. First off, it requires a XML file be created and passed to it so that it knows what files to analyze. Functional but certainly not something I like doing by hand and absolutely not something I want to had to maintain – I already have a script to maintain…my build script. So I figured I’d throw my hat in with the “custom NAnt task” folks and give it a shot. Attached it the code for the NDependTask that I created as a result. It’s certainly not perfect since it doesn’t handle the warn suppression quite as well as the original XML file did but it’s certainly a step in the right direction. I figure with a little better knowledge of NAnt and some work I should be able to put together the last few pieces and make it feature complete but it works for now. Just thought I’d share with anyone who was looking.

My thanks to James Geurts the author of the FxCopTask since I used that code as a general guideline when writing my NDependTask.

using System.Diagnostics;
using System.IO;
using System.Text;
using System.Xml;
using NAnt.Core;
using NAnt.Core.Attributes;
using NAnt.Core.Tasks;
using NAnt.Core.Types;

namespace NAnt.Contrib.Tasks
{
    /// <summary>
    /// Analyzes .NET assemblies and generates design quality metrics in
    /// terms of extensibility, maintainability, and reusability to
    /// effectively control and manage the assembilies dependencies.
    /// </summary>
    /// <remarks>
    /// Suppression of warns per assembly is not currently supported.
    /// Suppression of specific warns is not currently supported.
    /// </remarks>
    /// <example>
    ///        <code>
    ///            <![CDATA[
    ///            <ndepend program=”tools\ndepend\ndepend.console.exe” projectName=”MyProject” outputDir=”${build.reports}” warnNotUsedType=”false” deleteConfig=”true” runReport=”true”>
    ///                <directories>
    ///                    <include name=”${build.dir}” />
    ///                </directories>
    ///                <assembilies>
    ///                    <include name=”${build.dir}\*.dll” warn=”true” />
    ///                </assembilies>
    ///                <frameworkAssembilies>
    ///                    <include name=”${build.dir}\tests.dll” />
    ///                </frameworkAssembilies>
    ///            </ndepend>
    ///            ]]>
    ///        </code>
    /// </example>
    [TaskName( “ndepend” )]
    public class NDependTask : ExternalProgramBase
    {
        #region ” Fields “

        private string m_XmlConfigName;
        private bool m_DeleteConfig;
        private string m_ProjectName;
        private string m_OutputDir;
        private bool m_RunReport;
        private DirSet m_Directories;
        private FileSet m_Assembilies;
        private FileSet m_Framework;
        private bool m_WarnNotUsedType;
        private bool m_WarnNotUsedMember;
        private bool m_WarnTypeVisibility;
        private bool m_WarnPropertiesVisibility;
        private bool m_WarnMethodsVisibility;
        private bool m_WarnConstructorsVisibility;
        private bool m_WarnFieldsVisibility;
        private bool m_WarnEventsVisbility;

        private StringBuilder m_Arguments;

        #endregion

        #region ” Properties “

        public override string ProgramArguments
        {
            get { return m_Arguments.ToString(); }
        }

        [TaskAttribute( “program”, Required=false )]
        [StringValidator( AllowEmpty=false )]
        public override string ExeName
        {
            get { return base.ExeName; }
            set { base.ExeName = value; }
        }

        [TaskAttribute( “configFile”, Required=false )]
        public string XmlConfigName
        {
            get { return m_XmlConfigName; }
            set { m_XmlConfigName = value; }
        }

        [TaskAttribute( “deleteConfig”, Required=false )]
        [BooleanValidator()]
        public bool DeleteConfig
        {
            get { return m_DeleteConfig; }
            set { m_DeleteConfig = value; }
        }

        [TaskAttribute( “projectName”, Required=true )]
        [StringValidator( AllowEmpty=false )]
        public string ProjectName
        {
            get { return m_ProjectName; }
            set { m_ProjectName = value; }
        }

        [TaskAttribute( “outputDir”, Required=false)]
        [StringValidator( AllowEmpty=false )]
        public string OutputDir
        {
            get { return m_OutputDir; }
            set { m_OutputDir = value; }
        }

        [TaskAttribute( “runReport”, Required=false)]
        [BooleanValidator()]
        public bool RunReport
        {
            get { return m_RunReport; }
            set { m_RunReport = value; }
        }

        [BuildElement( “directories”) ]
        public DirSet Directories
        {
            get { return m_Directories; }
            set { m_Directories = value; }
        }

        [BuildElement( “assembilies”)]
        public FileSet Assembilies
        {
            get { return m_Assembilies; }
            set { m_Assembilies = value; }
        }

        [BuildElement( “frameworkAssembilies”)]
        public FileSet Framework
        {
            get { return m_Framework; }
            set { m_Framework = value; }
        }

        [TaskAttribute( “warnNotUsedType”, Required=false )]
        [BooleanValidator()]
        public bool WarnNotUsedType
        {
            get { return m_WarnNotUsedType; }
            set { m_WarnNotUsedType = value; }
        }

        [TaskAttribute( “warnNotUsedMember”, Required=false )]
        [BooleanValidator()]
        public bool WarnNotUsedMember
        {
            get { return m_WarnNotUsedMember; }
            set { m_WarnNotUsedMember = value; }
        }

        [TaskAttribute( “warnTypeVisibility”, Required=false )]
        [BooleanValidator()]
        public bool WarnTypeVisibility
        {
            get { return m_WarnTypeVisibility; }
            set { m_WarnTypeVisibility = value; }
        }

        [TaskAttribute( “warnPropertiesVisibility”, Required=false )]
        [BooleanValidator()]
        public bool WarnPropertiesVisibility
        {
            get { return m_WarnPropertiesVisibility; }
            set { m_WarnPropertiesVisibility = value; }
        }

        [TaskAttribute( “warnMethodsVisibility”, Required=false )]
        [BooleanValidator()]
        public bool WarnMethodsVisibility
        {
            get { return m_WarnMethodsVisibility; }
            set { m_WarnMethodsVisibility = value; }
        }

        [TaskAttribute( “warnConstructorsVisibility”, Required=false )]
        [BooleanValidator()]
        public bool WarnConstructorsVisibility
        {
            get { return m_WarnConstructorsVisibility; }
            set { m_WarnConstructorsVisibility = value; }
        }

        [TaskAttribute( “warnFieldsVisibility”, Required=false )]
        [BooleanValidator()]
        public bool WarnFieldsVisibility
        {
            get { return m_WarnFieldsVisibility; }
            set { m_WarnFieldsVisibility = value; }
        }

        [TaskAttribute( “warnEventsVisbility”, Required=false )]
        [BooleanValidator()]
        public bool WarnEventsVisbility
        {
            get { return m_WarnEventsVisbility; }
            set { m_WarnEventsVisbility = value; }
        }

        #endregion

        #region ” Constructors “

        /// <summary>
        /// Creates a new <see cref=”NDependTask” /> instance.
        /// </summary>
        public NDependTask()
        {
            m_Arguments = new StringBuilder();
            m_Directories = new DirSet();
            m_Assembilies = new FileSet();
            m_Framework = new FileSet();

            // Default values.
            ExeName = “ndepend.console.exe”;
            m_XmlConfigName = “ndepend.xml”;
            m_WarnConstructorsVisibility = true;
            m_WarnEventsVisbility = true;
            m_WarnFieldsVisibility = true;
            m_WarnMethodsVisibility = true;
            m_WarnNotUsedMember = true;
            m_WarnNotUsedType = true;
            m_WarnPropertiesVisibility = true;
            m_WarnTypeVisibility = true;
        }

        #endregion

        #region ” Methods “

        /// <summary>
        /// Performs logic before the external process is started
        /// </summary>
        /// <param name=”process”>Process.</param>
        protected override void PrepareProcess( Process process )
        {
            BuildConfig();
            BuildArguments();

            Log( Level.Verbose, “Working directory: {0}”, process.StartInfo.WorkingDirectory );
            Log( Level.Verbose, “Arguments: {0}”, ProgramArguments );

            base.PrepareProcess( process );
        }

        protected override Process StartProcess()
        {
            Process process = base.StartProcess ();
            process.Exited += new System.EventHandler(process_Exited);
            return process;
        }

        /// <summary>
        /// Removes generated config file after process has run.
        /// </summary>
        /// <param name=”sender”></param>
        /// <param name=”e”></param>
        private void process_Exited(object sender, System.EventArgs e)
        {
            string filename = GetConfigFilename();
            if ( File.Exists( filename ) && m_DeleteConfig )
            {
                Log( Level.Verbose, “Deleting config file.” );
                File.Delete( GetConfigFilename() );
            }
        }

        /// <summary>
        /// Concatenates variables for complete filename.
        /// </summary>
        /// <returns>Configuration filename</returns>
        private string GetConfigFilename()
        {
            if ( m_OutputDir.Length > 0 )
            {
                if ( !Directory.Exists(m_OutputDir) )
                {
                    Directory.CreateDirectory( m_OutputDir );
                }
                return Path.Combine( m_OutputDir, m_XmlConfigName );
            }
            else
            {
                return m_XmlConfigName;
            }
        }

        /// <summary>
        /// Build the arguments to pass to the executable.
        /// </summary>
        private void BuildArguments()
        {
            m_Arguments.Append( GetConfigFilename() );

            if ( m_RunReport )
            {
                m_Arguments.Append( ” /ViewReport” );
            }
        }

        /// <summary>
        /// Build the Xml config file to pass to the executable.
        /// </summary>
        private void BuildConfig()
        {
            XmlDocument config = new XmlDocument();
            // Not sure why this line is giving me an error.
//            config.AppendChild(config.CreateProcessingInstruction( “1.0”, “” ));
            XmlElement element = config.CreateElement( “NDepend” );
            XmlAttribute attribute = config.CreateAttribute( “AppName” );
            attribute.Value = m_ProjectName;
            element.Attributes.Append( attribute );
            XmlNode root = config.AppendChild( element );
           
            XmlNode node = root.AppendChild( config.CreateElement( “Dirs” ) );
            foreach( string directory in m_Directories.Includes )
            {
                element = config.CreateElement( “Dir” );
                element.InnerText = directory;
                node.AppendChild( element );
            }

            node = root.AppendChild( config.CreateElement( “Assemblies” ) );
            foreach( string file in m_Assembilies.Includes )
            {
                string assemblyName = file;
                if ( file.EndsWith( “.dll” ))
                {
                    assemblyName = file.Substring(0, file.LastIndexOf( ‘.’ ));
                }
                element = config.CreateElement( “Name” );
                element.InnerText = assemblyName;
                node.AppendChild( element );
            }

            node = root.AppendChild( config.CreateElement( “FrameworkAssemblies” ) );
            foreach( string file in m_Framework.Includes )
            {
                string assemblyName = file;
                if ( file.EndsWith( “.dll” ))
                {
                    assemblyName = file.Substring(0, file.LastIndexOf( ‘.’ ));
                }
                element = config.CreateElement( “Name” );
                element.InnerText = assemblyName;
                node.AppendChild( element );
            }

            node = root.AppendChild( config.CreateElement( “WarnFilter” ) );

            if ( !m_WarnNotUsedType )
            {
                attribute = config.CreateAttribute( “warnNotUsedType” );
                attribute.Value = “false”;
                node.Attributes.Append( attribute );
            }

            if ( !m_WarnNotUsedMember )
            {
                attribute = config.CreateAttribute( “warnNotUsedMember” );
                attribute.Value = “false”;
                node.Attributes.Append( attribute );
            }

            if ( !m_WarnTypeVisibility )
            {
                attribute = config.CreateAttribute( “warnTypeVisibility” );
                attribute.Value = “false”;
                node.Attributes.Append( attribute );
            }

            if ( !m_WarnPropertiesVisibility )
            {
                attribute = config.CreateAttribute( “warnPropertiesVisibility” );
                attribute.Value = “false”;
                node.Attributes.Append( attribute );
            }

            if ( !m_WarnMethodsVisibility )
            {
                attribute = config.CreateAttribute( “warnMethodsVisibility” );
                attribute.Value = “false”;
                node.Attributes.Append( attribute );
            }

            if ( !m_WarnConstructorsVisibility )
            {
                attribute = config.CreateAttribute( “warnConstructorsVisibility” );
                attribute.Value = “false”;
                node.Attributes.Append( attribute );
            }

            if ( !m_WarnFieldsVisibility )
            {
                attribute = config.CreateAttribute( “warnFieldsVisibility” );
                attribute.Value = “false”;
                node.Attributes.Append( attribute );
            }

            if ( !m_WarnEventsVisbility )
            {
                attribute = config.CreateAttribute( “warnEventsVisbility” );
                attribute.Value = “false”;
                node.Attributes.Append( attribute );
            }

            if ( node.Attributes.Count == 0 )
            {
                root.RemoveChild( node );
            }

            config.Save( GetConfigFilename() );
        }

        #endregion

    } // end class

} // end namespace

Advertisements

Pure Genius…

October 21, 2005

MillionDollarHomepage has to the most creative advertising campaign I’ve ever seen. It was started by a guy from the UK just hoping to pay for college (and socks) and while it is only a few months old, it has already yielded over $400K. I think University will be covered. It just goes to show that there are still ideas out there and with a little effort, anyone can capitalize on them. Now, if you’ll excuse me, I have to go and figure out what’s going to make my next million… 😉


Life Hacking…

October 18, 2005

Meet the Life Hackers (NYT – no reg needed) by way of Signal vs. Noise

Synergy2 – For sharing a single KVM across multiple systems including different operating systems (supports *nix, Mac, Win)


Asserting Myself…

October 17, 2005

I’ve been pretty heavy into NUnit lately. I’m trying to get better about actually writing the unit tests before (or at least at the same time) that I write the code. It’s really been helpful as I reconfigure my classes and such and I’ve even started to go back and put some tests in older code that I’ve written. Here’s the main issue that I’ve hit right now and I thought I’d throw it out there for comment in case anyone else has run into.

I have a class that is event driven and I wanted to test those events so I wrote the following unit test:

[Test]
public void ChangeStatus_UserUpdate()
{
MyClient client = new MyClient();
client.OnEventFire += new FireEventHandler( OnEventFired );
client.FireEvent();
Assert.AreEqual(1, Assert.Counter);
}

private void OnEventFired( object sender, FireEventArgs e )
{
Assert.AreEqual( 1, e.Value );
}

Looks fairly easy right?? Unfortunately, there are a few factors working against me here. I know that having multiple asserts in a single test is bad but it’s what I’m having to do to get this to test. Anyone with a better idea, please let me know. The second and biggest problem is that Assert.Counter does not seem to reset before each test is called. Not quite sure why but sometimes I get 3, sometimes it’s 2, sometimes it’s 0. Originally, I thought it was when I was running the whole fixture as opposed to just one test but that didn’t pan out. For now I’ll stick to the “if it works, use it” mentality unless something better comes along.


What happened…

October 11, 2005

Well, apparently I’m not allowed to run a webserver at the house anymore. Charter decided, after 2 years without issue, to shut off our service after “discovering” a webserver on my IP. Wonder if it has anything to do with the fact that we had several of their folks in and out of our house for 10 days…Oh well. So for now, I’ve moved the blog to here. I will be finding another home for it but since I had to promise to remove the server in order for them to restore my service, I’m using this as a quick fix. Who knows, maybe I’ll just leave it so I can test out the new toys. 🙂