Introduction
This article explains the steps to generate MSDN-style documentation with Sandcastle, using NAnt, a scripting language. The scripts were developed to be started from a CruiseControl.NET project, but you can easily start them directly from NAnt as well.
Background
Sandcastle is Microsoft's new generator for MSDN-style documentation. The Sandcastle components gather information about your assemblies through Reflection, and combine this with the XML comments from your source code. The output is a set of HTML pages that you can optionally compile to CHM or HxS format. The NAnt scripts that I propose in this article are part of a CruiseControl.NET project, and closely follow the 12-step procedure to generate a CHM as elaborated in this official Sandcastle blog entry, and graphically represented in the image above.
Preparation
The presented set of NAnt scripts will work under the following assumptions:
- The application for which you want to generate the documentation is successfully compiled. Your own assemblies are stored in a directory of which the path is stored in a NAnt property named
${bin.intern.dir}
- The extracted XML documentation files are located in the directory referred to by the
${documentation.dir}
property - they were generated by using the /doc
command line argument of your favorite compiler - All dependency third-party assemblies (e.g. Infragistics, Composite UI Application Block, Liquid XML Library, Wilson OR Mapper) are located in a single directory of which the path is stored in the
${bin.extern.dir}
property.
Code
Properties Section
To keep the core of the buildscripts readable and maintainable, let's first create some properties for all fixed paths to directories, executables, and XSL transformations:
<property name="sandcastle.dir"
value="F:\Program Files\Sandcastle" />
<property name="sandcastle.workingdir"
value="${projects.dir}\${CCNetProject}\SandcastleWorkingDir" />
<property name="sandcastle.output.dir"
value="${sandcastle.workingdir}\Output" />
<property name="sandcastle.mrefbuilder.exe"
value="${sandcastle.dir}\productiontools\mrefbuilder.exe" />
<property name="sandcastle.buildassembler.exe"
value="${sandcastle.dir}\productiontools\buildassembler.exe" />
<property name="sandcastle.xsltransform.exe"
value="${sandcastle.dir}\productiontools\xsltransform.exe" />
<property name="sandcastle.addoverloads.xsl"
value="${sandcastle.dir}\ProductionTransforms\AddOverloads.xsl" />
<property name="sandcastle.addguidfilenames.xsl"
value="${sandcastle.dir}\ProductionTransforms\AddGuidFilenames.xsl" />
<property name="sandcastle.reflectiontomanifest.xsl"
value="${sandcastle.dir}\ProductionTransforms\ReflectionToManifest.xsl" />
<property name="sandcastle.reflectiontochmproject.xsl"
value="${sandcastle.dir}\ProductionTransforms\ReflectionToChmProject.xsl" />
<property name="sandcastle.reflectiontochmcontents.xsl"
value="${sandcastle.dir}\ProductionTransforms\ReflectionToChmContents.xsl" />
<property name="sandcastle.reflectiontochmindex.xsl"
value="${sandcastle.dir}\ProductionTransforms\ReflectionToChmIndex.xsl" />
<property name="hhc.exe" overwrite="false"
value="F:\Program Files\HTML Help Workshop\hhc.exe" />
Create an Empty Working Directory
The first task in the NAnt Project is the creation of an empty working directory:
<mkdir dir="${sandcastle.workingdir}"
if="${not directory::exists(sandcastle.workingdir)}" />
<delete>
<fileset basedir="${sandcastle.workingdir}">
<include name="**\*" />
</fileset>
</delete>
Prepare the Input for the BuildAssembler
The Sandcastle component that creates the actual HTML documentation is the so-called BuildAssembler. It takes three input files:
- reflection.xml
- sandcastle.config
- manifest.xml
The following steps create these files:
Create the Configuration File
This NAnt-task copies the original sandcastle.config file to the working directory. The relative paths in the original file are substituted by fixed paths, and we replace the built-in reference to a comments.xml file with a reference to the folder containing the XML documentation of our own assemblies:
<copy file="${sandcastle.dir}/Presentation/Configuration/Sandcastle.config"
tofile="${sandcastle.workingdir}/Sandcastle.config">
<filterchain>
<replacestring from=""..\..\" to=""${sandcastle.dir}\" />
<replacestring from=""..\" to=""${sandcastle.dir}\Examples\" />
<replacestring from=""comments.xml" to=""${documentation.dir}\*.xml" />
</filterchain>
</copy>
Create the Initial Reflection File
MRefBuilder is one of the core Sandcastle console applications. It uses reflection to create an XML file that describes the inner structure of a set of assemblies. Its complete command line contains:
- The list of assemblies (wildcards are supported)
- /out: The XML output file
- /dep: Comma-separated list of dependant assemblies (wildcards are allowed)
- /internal+|- to specify whether to generate internal as well as external APIs
<exec program="${sandcastle.mrefbuilder.exe}" workingdir="${sandcastle.workingdir}">
<arg value="${bin.intern.dir}/*.dll" />
<arg value="${bin.intern.dir}/*.exe" />
<arg value="/out:reflection.org1.xml" />
<arg value="/dep:${bin.extern.dir}\*.dll" />
</exec>
The file that was generated by MRefBuilder -reflection.org- contains two types of XML elements:
<assembly>
: assembly metadata (version, description, company, ...) <api>
: namespaces, types, members, constructors, ...
Create the Final Reflection.xml File
These tasks create the final Reflection.xml file, containing all the necessary information, but no presentation at all. Using Sandcastle's XSLTransform tool, we apply two XSL transformations on the reflection.org file:
- The original list methods and constructors are flat, first we have to group these topics by overloads
- Then, we add the names of the HTML-file for each topic
It is possible to apply the two transformations in one step, but I've split them to easily verify the results:
<exec program="${sandcastle.xsltransform.exe}" workingdir="${sandcastle.workingdir}">
<arg value="reflection.org1.xml" />
<arg value="/xsl:"${sandcastle.addoverloads.xsl}"" />
<arg value="/out:reflection.org2.xml" />
</exec>
<exec program="${sandcastle.xsltransform.exe}" workingdir="${sandcastle.workingdir}">
<arg value="reflection.org2.xml" />
<arg value="/xsl:"${sandcastle.addguidfilenames.xsl}"" />
<arg value="/out:reflection.xml" />
</exec>
If you want to expose this directory to the end-users of your application, then you would probably not use GUIDs as file name. I suggest to create your own transformation as an alternative for addguidfilenames.xsl.
Create the Manifest
The so-called Manifest is again an XML file. It's a list of Topic entries, one for each <api>
-element from reflection.xml.
<exec program="${sandcastle.xsltransform.exe}" workingdir="${sandcastle.workingdir}">
<arg value="/xsl:"${sandcastle.reflectiontomanifest.xsl}"" />
<arg value="reflection.xml" />
<arg value="/out:manifest.xml" />
</exec>
Generate the HTML Documentation
Prepare the Output Environment
The output directory has four subfolders. The BuildAssembler tool will generate its documentation in the html subfolder, the art, scripts, and styles folder are copied from the installation. They contain the necessary scripts, stylesheets, and images to give the documentation its MSDN look-and-feel.
<mkdir dir="${sandcastle.output.dir}" />
<mkdir dir="${sandcastle.output.dir}/html" />
<copy todir="${sandcastle.output.dir}">
<fileset basedir="${sandcastle.dir}/Presentation">
<include name="art/*" />
<include name="scripts/*" />
<include name="styles/*" />
</fileset>
</copy>
Generate HTML Documentation
BuildAssembler is a generic console-application. It sends an XML document (reflection.xml) through a pipeline of components (like transformators, file creators, flow controllers, ...). The pipeline is constructed via the sandcastle.config, and is executed for each topic (i.e. each entry in the manifest.xml). BuildAssemblers command line consists of references to the manifest.xml file and sandcastle.config. The links to reflection.xml and the files containing the XML source code documentation are stored in the config file.
<exec program="${sandcastle.buildassembler.exe}" workingdir="${sandcastle.workingdir}" >
<arg value="manifest.xml" />
<arg value="/config:Sandcastle.config" />
</exec>
The output of this process is a directory with linked HTML-file for each topic. The files in the arts, scripts, and styles folders apply an MSDN look-and-feel. The documentation is fully functional, the rest of the process is optional.
Generate the CHM File
The next tasks transform the current Output directory to a single CHM file, through HTML Help Workshop.
Create the Input for the HTML Help Compiler
The HTML Help compiler expects three input files:
- A file with the contents (*.hhp),
- A file with the Table of Contents (*.hhc)
- An Index file (*.hhk)
The input files are all XML files that can be generated by applying an XSL transformation on reflection.xml:
<exec program="${sandcastle.xsltransform.exe}" workingdir="${sandcastle.workingdir}">
<arg value="/xsl:"${sandcastle.reflectiontochmproject.xsl}"" />
<arg value="reflection.xml" />
<arg value="/out:"${sandcastle.output.dir}\test.hhp"" />
</exec>
<exec program="${sandcastle.xsltransform.exe}" workingdir="${sandcastle.workingdir}" >
<arg value="/xsl:"${sandcastle.reflectiontochmcontents.xsl}"" />
<arg value="reflection.xml" />
<arg value="/arg:html=Output\html" />
<arg value="/out:"${sandcastle.output.dir}\test.hhc"" />
</exec>
<exec program="${sandcastle.xsltransform.exe}" workingdir="${sandcastle.workingdir}" >
<arg value="/xsl:"${sandcastle.reflectiontochmindex.xsl}"" />
<arg value="reflection.xml" />
<arg value="/out:"${sandcastle.output.dir}\test.hhk"" />
</exec>
Compile the Help Project
Finally, we compile the project to a CHM file through the Microsoft HTML Help Compiler (v1.4). I had to put the failonerror
on false
to ignore the non-zero return value.
<exec program="${hhc.exe}"
commandline="test.hhp"
workingdir="${sandcastle.output.dir}"
failonerror="false"/>
History
- 4th September, 2006: Initial version of this article
About Diederik Krols
Diederik Krols is a .NET Architect and Trainer. As a Certified Scrum Master he embraces all tools that keep the development pace and quality as high as possible, and the overhead as low as possible. He has posted many articles on CruiseControl.NET and other components of the Open Source N*Stack (NAnt, NUnit, NCover, ...) on the Real Developer Network. His personal blog contains articles on Anti-Patterns.