Managing .NET Development with NAnt

来源:百度文库 编辑:神马文学网 时间:2024/04/29 09:04:15

January 29, 2004

Why NAnt

Microsoft has released their most complete and powerful IDE yet inVisual Studio .NET. From the vast library of project templates, to theincredibly easy to use WinForms designer, to solid tools for visuallyintegrating databases, VS.NET provides an excellent environment for theindividual developer.

There is a lot more to creating an application than just writing andcompiling code, however. Visual Studio .NET, for all its powerfulfeatures, cannot be all things to all developers. Specifically, VisualStudio .NET provides little support for:

  • Customized deployment scenarios
  • Distributed team development
  • Centralized integration and compilation
  • Automated testing and reporting
  • Source control tools, other than Visual Source Safe
  • Automated pre- and post-processing of project files

For example, let's say you are working on a project with 9 otherprogrammers. Five of you are in Los Angeles, and five in Boston. TheSourceSafe server is in Chicago, as is the staging server. When youwant to work on a single code file, it's as easy as checking it out,editing it, and checking it in. But what if you need to do a sanitycheck of your file as part of the build? Well, then you have to gothrough the SourceSafe database and check out all the different projecttrees, and build them (in the correct order) just to make sure yourcomponent compiles ok. Then, if you want to run unit tests, you have tofire up NUnit and walk through all the test libraries manually.

Your job as the build engineer is even more difficult. Somehow, youhave to find a way to check out the full version of the project everynight at midnight, build everything (in the correct order), run the unittests, email the compile and test results to every member of the team,and if everything else went ok, zip the binaries up and ftp them to the distribution server.

Visual Studio provides some of the support for the first scenario(developer on a team) and almost none for the second (build engineer).If you have to execute these tasks manually, you open yourself to a slewof errors (missed files in the checkout, forgotten test libraries,mistyped commands). These repetitive tasks cry out for automation.Historically, this kind of automation was handled with tools like make and nmake.These tools rely on a proprietary syntax for writing a build file whichexecutes a series of commandline tools to build your project.

NAnt is an open source alternative to make. NAnt is easy to learn,stable, efficient, and best of all, free. It stands above other buildtools because it uses XML for its file structure, which is more easilyreadable by humans and manipulated by code. Additionally, there is arobust mechanism for testing the success of each individual build stepand halting the process upon failure.

For an individual developer, NAnt is an important tool for managing a project; for a distributed team, NAnt is essential.


An Introduction to NAnt

NAnt is deceptively simple. An NAnt build file is comprised of fourkinds of items: tasks, targets, properties and projects. Everythingthat can be done with NAnt is done with one of these four types. Allare represented in a standard XML syntax.


Tasks

Tasks are the NAnt version of commands. Most NAnt tasks come with NAntitself, though NAnt is extensible through third-party command librariesor writing your own. Tasks can represent simple shell commands (copy, delete, touch), complex system commands (zip, unzip, mail, cvs), .NET-specific commands (csc, vbc, tlbimp), and miscellaneous commands (nunit, regex).

NAnt also has tasks that make it operate somewhat like a declarative language: tasks like if, foreach, and script allow you to insert flow-control and looping structures, as well as arbitrary executable script statements into the build file.

A task is a specifically named XML element, with standardized attributesand sub-elements that make up the options for the represented command.For example, to make a backup copy of an important file, you would usethe copy task:


or, to send an email, you would use the mail task:

tolist=devteam@mycompany.com
subject="Nightly build results"
mailhost="smtp.mycompany.com">





The full list of standard tasks is available at http://nant.sourceforge.net/help/tasks/index.html. The NAntContrib project (http://nantcontrib.sourceforge.net) provides a wide variety of additional tasks, from reporting project statistics (codestats) to interacting with IIS (mkiisdir, deliisdir) to executing SQL statements (SQL).


Targets

Targets are named collections of tasks. Think of tasks as individualcommands, and targets as named methods. Executing a target causes eachtask contained in that target to execute, in the order they are found.






Targets can depend on other targets to execute. Using the depends attribute, you can specify one or more other targets that must execute prior to the current target. The dependsattribute takes a comma-separated list of target names as its value;the targets listed will be executed in order from left to right.

It is an immutable rule of NAnt that a target is executed only onceduring a given build event. Even if multiple executing targets dependon one shared target, the shared target will execute only once (when thefirst dependent target is executed). Subsequent dependent targets willsee that the requested target has executed already.












Whenever the prepare target executes, NAnt will verify that clean has already run. If it has, prepare is allowed to continue; if it has not, clean is run first, then prepare. Keep in mind that regardless of how many targets depend on the same target, each target is executed only once.


Properties

Properties are named data storage for a given build file. Though youcan think of them as variables, that isn't technically correct sinceproperty values can be set exactly once; thereafter, a property value isimmutable. Properties can be supplied a default value in the buildfile, or can be read from an external file or have their values suppliedby arguments on the command that launched the build.

NAnt supplies a series of default properties available to any project, from nant-specific information (like nant.version, nant.filename) to .NET-specific information (like nant.settings.defaulframework and nant.settings.defaultframework.sdkdirectory). You can create your own properties by supplying a name and optional default value.




Projects

A project is a collection of properties, targets and tasks. A projectcan reside in a single XML (build) file, or can span multiple files. Itdefines the collection of targets and properties needed to manage yourdevelopment project.














...

...

Projects have three attributes that you can modify: name, default, and basedir. Name is an identifying string (see above example). Default is the name of the target to execute if none is otherwise supplied. Basedir is the location to use as the root of any relative file system operations; if you do not supply a value for basedir, NAnt will use the folder containing the executing build file.


Running Projects

To run a project, invoke nant.exe and pass in the name of the build file using the /f flag:

C:>nant /f:default.build

If you omit the build file name, NAnt uses the following algorithm to determine what to execute:

  1. Search the current folder for a build file.
  2. If no build file is found, return an error.
  3. If only one build file is found, execute it.
  4. If more than one build file is found:
    • If one is called default.build, execute it.
    • If none are called default.build, return an error.

In addition to build file, you can pass in a variety of otherinformation to nant, including the target framework version, a log fileto output to, whether or not to display debug information in the output,etc. Perhaps most important, you can pass in values for properties inthe build file using the -D flag. For instance, to run myProject above and override the default value of project.author, you could issue the following command:

C:>nant /f:myProject.build -D:project.author="Albert Einstein"

In addition, you can specify a target to execute. If you do not specifyone, the value of the default attribute of the project will be used. Tospecify a different target, just append the target names you desire in alist as the final arguments on the nant command:

C:>nant -D:project.author="Albert Einstein" clean prepare

NAnt and the Visual Studio Project File

A very common use for a build file is to compile a project; .NETdevelopers commonly use Visual Studio .NET to compile their project, butcan also use the command-line compilers (csc.exe, vbc.exe, etc.).Visual Studio .NET manages the complexities of compilation, such asexternal references and output types. Using the command-line compilersrequires managing those complexities yourself.


Inside the Visual Studio Project File

The Visual Studio .NET project file (i.e. *.csproj) is an XML filedescribing the files and settings that make up a given project. Insidethe root element is an element describing the type of project file; or , for instance.

Beneath this element are two sections: Build and Files. Files is a simple list of all associated files and how to treat them.



Build contains two further subsections: Settings and References.Settings captures all the information you can set in the Project ->Properties dialog, including global settings plus anything specific tothe Debug or Release builds. References lists all the externalassemblies that this project depends upon.

HintPath="..\..\WINDOWS\Microsoft.NET\Framework\v1.1.4322\System.dll"/>
HintPath="\Program Files\NUnit V2.1\\bin\nunit.framework.dll"/>

Together, Visual Studio .NET uses all of this data to create the appropriate compilation command to create your project.


Avoid Executing Devenv.exe

Many beginning NAnt users make the mistake of piggy-backing on VisualStudio .NET (devenv.exe) to compile their projects. Since the VisualStudio .NET project file already contains all the necessary data aboutthe project, it is easy to invoke devenv.exe using the exec task andpoint it at the project file.

There are two significant drawbacks to this style of compilation:

  1. The overhead cost of running Visual Studio .NET is quite large. Any process that invokes it is going to drain a lot of system resources. An automated script (like a NAnt build file) that may be invoked often, can quickly run into resource problems.
  2. Not all machines that need to build the project will have Visual Studio .NET installed. Any central build-and-test server, in fact, should NOT have it installed.

Avoid using devenv.exe to compile your projects. Instead, use NAnt'sbuilt-in csc and vbc tasks. Doing so means that build machines need onlyhave the .NET Framework installed (a given for .NET projects) and usethe much more efficient command-line compilers to do their work.

The problem with this approach is the complexity of the csc and vbctasks once your project expands beyond a simple HelloWorld. Not only dothe tasks have 21 possible attributes you can, and in some cases must,set, but it has sub-elements for:

  • Search paths for referenced assemblies
  • The referenced assemblies themselves
  • Embedded resources
  • All source files
  • And any other arbitrary arguments to the compile command

As your project changes over time, you have to keep your build file insynch with the current state. This is tedious, and doing it manually iserror-prone.


Using SLiNgshoT

Luckily, there are other options for keeping the build file in synchwith the project file. The NAntContrib project comes with a tool calledSLiNgshoT which will parse the project file and create a skeleton buildfile for you. The resulting file contains fully formatted csc (or vbc)tasks as appropriate, in addition to a variety of other default targetswhich you can modify to your liking.

SLiNgshot does a good job of quickly creating skeletal build files foryou; however, it is difficult to use SLiNgshoT with a project thatchanges often, since it regenerates the entire build file. Any editsyou made to the last version are lost; you have to copy them back inyourself, which is again tedious and error prone. In addition,regardless of how correct the resulting build file is, you are stuckwith the structure it provides (unless you undertake significantediting).


Using a Custom Transform

A more powerful alternative is to use a custom XSLT transform to createthe compilation task for you automatically. It can be used to eithercreate a stand-alone build file, or to modify the existing build-file bysimply replacing the existing compile statements.

Gordon Weakliem has an excellent entry on this topic on his blog (http:://radio.weblogs.com/0106046/stories/2002/08/10/supportingVsnetAndNant.html).


Using the Solution task (NAnt 0.8.3 or later)

Starting in release 0.8.3-rc1, the NAnt core tasks include . This task processes a given solution file, compiling the project(s) contained within. The task recognizes inter-project dependencies and compiles them in the correct order. The task does not invoke devenv.exe, but rather parses the solution file and invokes the command-line compiler.

The task allows you to provide specific buildinformation on a per-configuration basis (different arguments for debugvs. release). You can limit which projects in the solution to build bypassing in a list of projects in a element, or a list of projects to exclude in .

If the projects in your solution depend on the output of anotherproject (from a different solution) you can make them depend on itsoutput via the element. This allowsyou to specify any number of project files that need to be fullycompiled before your solution file can itself be compiled. NAnt willcheck to see if the referenced projects' binary output is up-to-date; ifso, the target solution will reference the existing assemblies.Otherwise, the referenced projects are compiled first.

Additionally, you can control the search paths for referenced assemblies using the element. Use this task when your target solution relies on third-partyassemblies that exist only in binary format. This element allows you toprovide the compiler hints about where referenced assemblies may befound on the file system.

The task is the easiest solution forintegrating with Visual Studio .NET project files, but it requires thatyour project have an actual solution file (.sln) instead of just aproject file (.csproj, .vbproj). If you require more control over thebuild parameters than is provided by this task, you are better offwriting a custom transform as in the previous section.


NAnt and the Distributed Team

NAnt is very useful for single-developer projects. You can easilycontrol everything from building deliverables to creating documentationto deployment from a single build file. However, as the developmentteam grows and the complexity of the application increases, NAnt becomeseven more useful. Integrated development environments like VisualStudio .NET simply can't manage all aspects of such a project. NAnt, as atool and an extensible framework, offers the perfect platform forcontrolling the complexity.

Enterprise-level applications are often a collection of interrelatedsubprojects. They typically contain some kind of client application, adatabase, and any number of layers of code in between the two.Individual developers may work on a single subproject, or might berequired to work in any or all of them depending on project need. NAntprovides a mechanism for ensuring that all developers work with theright files and perform the right pre- and post-build actions to keep insynch with the rest of the team.


Standardized Build Files

In order to effectively use NAnt across multiple related projects,your developer team must define a standard set of targets that make up abuild file, suitable to your team's goals and the type of applicationyou are developing. The canonical set of primary targets for a buildfile (see Hatcher and Loughrin's Java Development with Ant) are:

  • Init: prepares your environment for a successful build (creates folders, copies initial files, etc.)
  • Build: actually compile the application, creating any distributable application files
  • Docs: create any documentation
  • Test: run the unit tests
  • Dist: distribute the final artifacts (binary application, source code, documentation)
  • Clean: remove any build artifacts, leaving just the source
  • All: run all of these targets, in this order.

Typically, all of these targets are part of a single dependency chainexcept Clean. So, Build depends on Init. Docs and Test depend onBuild, and Dist depends on Docs and Test. Clean, however, should beable to be run on its own so that, at any point, a developer caneliminate any cruft that might have built up in the developmentdirectories. The All target is then used to run all of the targets(assuming, of course, there are no failures).

Projects might contain any number of specialized targets that enablethese primary targets. Perhaps one project requires a series ofspecialized XSLT transforms to create the documentation (using the