Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / XML

Generating MSDN-style Documentation with Sandcastle, NAnt and CruiseControl.NET

3.81/5 (12 votes)
4 Sep 2006CPOL5 min read 1  
NAnt tasks for Running Sandcastle through CruiseControl.NET

Sample Image - ccnetsandcastle.jpg

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:

XML
<!-- Directories -->
<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" />
<!-- Executables -->
<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" />
<!-- Transformations -->
<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" />
<!-- Help Compiler -->
<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:

XML
<!-- Create or Cleanup 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:

  1. reflection.xml
  2. sandcastle.config
  3. 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:

XML
<!-- Copy configuration file, and hard code references -->
<copy file="${sandcastle.dir}/Presentation/Configuration/Sandcastle.config"
      tofile="${sandcastle.workingdir}/Sandcastle.config"> 
   <filterchain> 
      <replacestring from="&quot;..\..\" to="&quot;${sandcastle.dir}\" /> 
      <replacestring from="&quot;..\" to="&quot;${sandcastle.dir}\Examples\" />
      <replacestring from="&quot;comments.xml" to="&quot;${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
XML
<!-- Run MRefBuilder (introspection on assemblies) to create basic Reflection XML -->
<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:

XML
<!-- Create final Reflection XML -->
<!-- Regroup overloads -->
<exec program="${sandcastle.xsltransform.exe}" workingdir="${sandcastle.workingdir}">
   <arg value="reflection.org1.xml" />
   <arg value="/xsl:&quot;${sandcastle.addoverloads.xsl}&quot;" />
   <arg value="/out:reflection.org2.xml" />
</exec>
<!-- Create filenames for html documents -->
<exec program="${sandcastle.xsltransform.exe}" workingdir="${sandcastle.workingdir}">
   <arg value="reflection.org2.xml" />
   <arg value="/xsl:&quot;${sandcastle.addguidfilenames.xsl}&quot;" />
   <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.

XML
<!-- Create Manifest (list of Topics) -->
<exec program="${sandcastle.xsltransform.exe}" workingdir="${sandcastle.workingdir}">
   <arg value="/xsl:&quot;${sandcastle.reflectiontomanifest.xsl}&quot;" />
   <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.

XML
<!-- Create Output Environment -->
<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.

XML
<!-- Run BuildAssembler (create html topic files) -->
<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:

  1. A file with the contents (*.hhp),
  2. A file with the Table of Contents (*.hhc)
  3. An Index file (*.hhk)

The input files are all XML files that can be generated by applying an XSL transformation on reflection.xml:

XML
<!-- Create html Help project -->
<exec program="${sandcastle.xsltransform.exe}" workingdir="${sandcastle.workingdir}">
   <arg value="/xsl:&quot;${sandcastle.reflectiontochmproject.xsl}&quot;" />
   <arg value="reflection.xml" />
   <arg value="/out:&quot;${sandcastle.output.dir}\test.hhp&quot;" />
</exec>
<!-- Create html Help project Table Of Contents -->
<exec program="${sandcastle.xsltransform.exe}" workingdir="${sandcastle.workingdir}" >
   <arg value="/xsl:&quot;${sandcastle.reflectiontochmcontents.xsl}&quot;" />
   <arg value="reflection.xml" />
   <arg value="/arg:html=Output\html" />
   <arg value="/out:&quot;${sandcastle.output.dir}\test.hhc&quot;" />
</exec>
<!-- Create html Help project Index -->
<exec program="${sandcastle.xsltransform.exe}" workingdir="${sandcastle.workingdir}" >
   <arg value="/xsl:&quot;${sandcastle.reflectiontochmindex.xsl}&quot;" />
   <arg value="reflection.xml" />
   <arg value="/out:&quot;${sandcastle.output.dir}\test.hhk&quot;" />
</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.

XML
<!-- Generate CHM file -->
<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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)