Open Source .NET Development [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Open Source .NET Development [Electronic resources] - نسخه متنی

Brian Nantz

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید


Extending NAnt


When extending NAnt, be sure to use the latest NAnt build. Use the development lists to ensure that someone has not already created the functionality you desire. Also, consider if the task is platform-independent; if it is not, the task is probably more appropriate in NAntContrib.

NAnt Base Classes


Table 4.3 lists the available base classes to inherit from when extending NAnt. If you were building a compiler task (i.e., a jsharp or csc task), then you would inherit from TaskCompilerBase. Most tasks, however, just inherit from the Task base class.

Table 4.3. NAnt Base Classes

Class

Description

SourceForge.Nant.Task

The base class that all custom tasks must derive from in order to plug into the NAnt architecture.

SourceForge.Nant.Task.ExternalProgramBase

Executes an external application from the path.

SourceForge.Nant.Task.CompilerBase

Compiler tasks that use the path.

NAnt Tasks' Error Handling


If an error in your custom task occurs, you should throw a BuildException so that the error will automatically be sent to the builds log using Log4Net (see Chapter 7, "Continuous Integration," for more information). Since your task inherits from Task, the failonerror task will function as expected. If the flag is true, the build will fail. The more descriptive the error message, the better:

BuildException(String message, Location location, Exception e)

This gives a very verbose error including the message, the location in the build file, and exception information.

Enabling Your Task to Handle Multiple Versions of .NET


If you are using functionality that is only available in a specific .NET runtime version, check the .NET runtime to ensure the .NET version your task requires is being used.

if(this.Properties["nant.settings.currentframework.version"] != "1.1")
{
BuildException("Wrong .Net runtime");
}

Extending NAnt Example


Before we extend NAnt with an example task, Listing 4.20 shows a simple task for inserting an assembly into the Global Assembly Cache (GAC). Most tasks expect input through XML attributes, but a task can also use optionsets or built-in properties as input as well, as in Listing 4.20. Since our task is going to wrap an existing command-line tool, it's a good idea to look at how similar tasks are structured, so let's take a quick look at the GacTask (from NAntContrib), which also happens to wrap an executable, gacutil.exe, and several of its features.

Listing 4.20. NAntContrib's GAC Task

[TaskName("gac")]
public class GacTask :

ExternalProgramBase {
string _assemblyName = null;
bool _uninstall = false;
int _timeout = Int32.MaxValue;

[TaskAttribute("assembly", Required=true)]
public string AssemblyName {
get { return _assemblyName; }
set { _assemblyName = value; }
}

[TaskAttribute("uninstall")]

[BooleanValidator()]
public bool Uninstall {
get { return _uninstall; }
set { _uninstall = value; }
}

[TaskAttribute("timeout")]

[Int32Validator()]
public override int TimeOut {
get { return _timeout; }
set { _timeout = value; }
}
public override string ProgramFileName {
get { return "gacutil"; }
}
public override string ProgramArguments {
get {
string assemblyName = AssemblyName;
if (Uninstall) {
int dllExtension = assemblyName.ToUpper().IndexOf(".DLL");
if (dllExtension > -1) {
assemblyName = assemblyName.Substring(0, dllExtension);
}
return "/u " + assemblyName;
} else {
return "/i " + assemblyName;
}
}
}

protected override void ExecuteTask() {

Log.WriteLine(LogPrefix + "{0} {1}", Uninstall ? "Uninstalling" : "Installing"
, AssemblyName);

base.ExecuteTask();

}
}
}
Usage:

<gac assembly='hello,Version=1.0.0.1,Culture="de",PublicKeyToken=45e343aae32233ca'
uninstall="true"/>

[TaskName("gac")] defines the name of the task. The best practice is to always use lowercase.

[TaskAttribute("assembly", Required=true)] is a required attribute. It is senseless to call gacutil without an assembly to gac.

Also notice the validators. These tell NAnt to validate that the attribute is a valid Boolean or Integer input. The task's Execute method is called after all attributes are validated. This is where the actual task code exists. Let's use this as a template for our tasks.

Subversion Tasks

Subversion (SVN) is an Open Source Control Management (SCM) system available at http://subversion.tigris.org. It is similar to CVS (created by many of the same people) but with additional features not available under CVS's architecture. Since CVS tasks are located in NAnt proper, that is probably where the SVN tasks should go as well. Other SCM tasks probably should go in NAntContrib for two reasons. First, Visual Source Safe and some of the other SCMs are not platform-independent. Second, CVS and SVN are the only freely available SCMs. IGLOO (http://www.jalindi.com/igloo/) and AnkhSVN (http://ankhsvn.tigris.org/) are promising Open Source projects that are add-ins to Visual Studio.NET to better integrate with CVS and Subversion directly from the IDE. Arild Fines from the AnkhSVN project started a Subversion NAnt task, and that is what this example is based on.

Figure 4-3 is a simple design where an abstract base class holds all the common information like username, password, and derived classes for checking out of Subversion and updating it. The code for the Abstract class is in Listing 4.21, and the code for checking out of Subversion is in Listing 4.22.

Listing 4.21. Common SVN Base Class

namespace NAnt.SourceControl.Tasks
{
/// <summary>
/// A Nant task to check out from a SVN repository.
/// </summary>
public abstract class AbstractSvnTask :

Task
{
private string username = null;
private string password = null;
private string localDir = null;
private string url = null;
private int revision = -1;
private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(System
.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
/// <summary>
/// Initializes a new instance of the <see cref="AbstractCvsTask" />
/// class.
/// </summary>
protected AbstractSvnTask ()
{
}
/// <summary>
/// The local path to check out to.
/// </summary>

[TaskAttribute("destination", Required = true )]
public string LocalDir
{
get { return this.localDir; }
set { this.localDir = value; }
}
/// <summary>
/// The username to authenticate with.
/// </summary>

[TaskAttribute("username", Required=false)]
public string Username
{
get { return this.username; }
set { this.username = value; }
}
/// <summary>
/// The password to authenticate with.
/// </summary>

[TaskAttribute("password", Required=false)]
public string Password
{
get { return this.password; }
set { this.password = value; }
}
/// <summary>
/// The URL to check out from.
/// </summary>

[TaskAttribute("url", Required=true)]
public string Url
{
get { return this.url; }
set { this.url = value; }
}
/// <summary>
/// The revision to check out - defaults to HEAD.
/// </summary>

[TaskAttribute("revision", Required=false)]
public int Revision
{
get { return this.revision; }
set { this.revision = value; }
}

protected abstract void ExecuteSVNCommand();
protected override void ExecuteTask ()
{
}
#region class Context
protected class Context : NSvnContext
{
public Context( string logPrefix )
{
this.logPrefix = logPrefix;
}
protected override void NotifyCallback(NSvn.Core.Notification notification)
{
Log.WriteLine( "{0}Checked out {1}", this.logPrefix, notification.Path +
Environment.NewLine);
}
private string logPrefix;
}
#endregion
}
}

Listing 4.22. SVN Checkout Class

namespace NAnt.SourceControl.Tasks
{
/// <summary>
/// A Nant task to check out from a SVN repository.
/// </summary>

[TaskName( "svn-checkout" )]
public class SvnCheckoutTask : AbstractSvnTask
{
/// <summary>
/// The funky stuff happens here.
/// </summary>

protected override void ExecuteSVNCommand()
{
try
{
Revision revision = NSvn.Core.Revision.Head;
if ( this.Revision != -1 )
revision = NSvn.Core.Revision.FromNumber( this.Revision );
RepositoryDirectory dir = new RepositoryDirectory( this.Url, revision );
if( this.Verbose )
dir.Context = new Context( this.LogPrefix );
if ( this.Username != null && this.Password != null )
{
dir.Context.AddAuthenticationProvider(
new SimpleProvider( new SimpleCredential(this.Username, this.Password) ) );
}
dir.Checkout( this.LocalDir, true );
}
catch( AuthorizationFailedException )
{
throw new BuildException( "Unable to authorize against the repository." );
}
catch( SvnException ex )
{
throw new BuildException( "Unable to check out: " + ex.Message );
}
catch( Exception ex )
{
throw new BuildException( "Unexpected error: " + ex.Message );
}
}
}
}

Figure 4-3. UML Class Diagram of the Design of the SVN NAnt Tasks.

As you can see, NAnt is extremely extensible. The ease of creating tasks has been the key to NAnt's success. The Open Source community has contributed tasks that have elevated NAnt above any other build tool available.


    / 275