Using Sandcastle August CTP and MSBuild to produce CHM documentation automatically

First, we created a cmd file to do the build for us. Syntax for calling the file is Build.cmd "PathToBinaries" "Name of the documentation" (more on this later)

 @echo off
 PUSHD %1
 "%programfiles%\Sandcastle\ProductionTools\mrefbuilder.exe" *.dll /out:%~dp0\reflection.org /dep:%windir%\Microsoft.NET\Framework\v2.0.50727\*.dll,C:\deps\*.dll,%WINDIR%\assembly\GAC_MSIL\Microsoft.VisualStudio.QualityTools.UnitTestFramework\8.0.0.0__b03f5f7f11d50a3a\Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll
 IF ERRORLEVEL 1 EXIT /B 1
 POPD
 "%programFiles%\Sandcastle\ProductionTools\XslTransform.exe" /xsl:"%programfiles%\Sandcastle\ProductionTransforms\AddOverloads.xsl" /xsl:"%programfiles%\Sandcastle\ProductionTransforms\AddGuidFilenames.xsl" reflection.org /out:reflection.xml
 IF ERRORLEVEL 1 EXIT /B 1
 "%programFiles%\Sandcastle\ProductionTools\XslTransform.exe" /xsl:"%programfiles%\Sandcastle\ProductionTransforms\ReflectionToManifest.xsl" reflection.xml /out:manifest.xml
 IF ERRORLEVEL 1 EXIT /B 1
 if not exist Output mkdir Output
if not exist Output\html mkdir Output\html
if not exist Output\art mkdir Output\art
if not exist Output\scripts mkdir Output\scripts
if not exist Output\styles mkdir Output\styles
copy "%programfiles%\Sandcastle\Presentation\art\*" Output\art > NUL
copy "%programfiles%\Sandcastle\Presentation\scripts\*" Output\scripts > NUL
copy "%programfiles%\Sandcastle\Presentation\styles\*" Output\styles > NUL
 "%programfiles%\Sandcastle\ProductionTools\BuildAssembler.exe" /config:sandcastle.config manifest.xml
IF ERRORLEVEL 1 EXIT /B 1
 "%programFiles%\Sandcastle\ProductionTools\XslTransform.exe" /xsl:"%programfiles%\Sandcastle\ProductionTransforms\ReflectionToChmProject.xsl" reflection.xml /out:Output\%2.hhp
 IF ERRORLEVEL 1 EXIT /B 1
 "%programFiles%\Sandcastle\ProductionTools\XslTransform.exe" /xsl:"%programfiles%\Sandcastle\ProductionTransforms\ReflectionToChmContents.xsl" reflection.xml /arg:html=Output\html /out:Output\%2.hhc
 IF ERRORLEVEL 1 EXIT /B 1
 "%programFiles%\Sandcastle\ProductionTools\XslTransform.exe" /xsl:"%programfiles%\Sandcastle\ProductionTransforms\ReflectionToChmIndex.xsl" reflection.xml /out:Output\%2.hhk
 IF ERRORLEVEL 1 EXIT /B 1
 CD OUTPUT
 "%programfiles%\HTML Help Workshop\hhc.exe" %2.hhp
 CD ..

 

There is also a configuration file we're using referenced in our call to BuildAssembler.exe; here is that

 <configuration>
   <dduetools>
     <builder>
       <components>
         <!-- Create skeleton document -->
         <component type="Microsoft.Ddue.Tools.CopyFromFileComponent" assembly="%programfiles%\Sandcastle\ProductionTools\BuildComponents\BuildComponents.dll">
       <data file="%programfiles%\Sandcastle\Presentation\transforms\skeleton.xml" />
       <copy source="/*" target="/" />
     </component>
         <!-- Copy in reflection data -->
     <component type="Microsoft.Ddue.Tools.CopyFromIndexComponent" assembly="%programfiles%\Sandcastle\ProductionTools\BuildComponents\BuildComponents.dll">
           <index name="reflection" value="/reflection/apis/api" key="@id" cache="10">
             <data files="reflection.xml" />
           </index>
           <copy name="reflection" source="*" target="/document/reference" />
     </component>
     <!-- Copy in container data -->
     <component type="Microsoft.Ddue.Tools.CopyFromIndexComponent" assembly="%programfiles%\Sandcastle\ProductionTools\BuildComponents\BuildComponents.dll">
           <copy name="reflection" key="string(/document/reference/containers/container/@namespace)" source="*[not(local-name()='elements')]" target="/document/reference/containers/container[@namespace]" />
     </component>
     <component type="Microsoft.Ddue.Tools.CopyFromIndexComponent" assembly="%programfiles%\Sandcastle\ProductionTools\BuildComponents\BuildComponents.dll">
           <copy name="reflection" key="string(/document/reference/containers/container/@type)" source="*[not(local-name()='elements')]" target="/document/reference/containers/container[@type]" />
     </component>    
     <!-- Generate syntax -->
     <component type="Microsoft.Ddue.Tools.IfThenComponent" assembly="%programfiles%\Sandcastle\ProductionTools\BuildComponents\BuildComponents.dll">
       <if condition="not(starts-with($key,'Overload:') or starts-with($key,'R:'))" />
           <then>
       <component type="Microsoft.Ddue.Tools.SyntaxComponent" assembly="%programfiles%\Sandcastle\ProductionTools\BuildComponents\BuildComponents.dll">
         <syntax input="/document/reference" output="/document/syntax" />
         <generators>
               <generator type="Microsoft.Ddue.Tools.CSharpDeclarationSyntaxGenerator" assembly="%programfiles%\Sandcastle\ProductionTools\BuildComponents\SyntaxGenerators.dll" />
               <generator type="Microsoft.Ddue.Tools.VisualBasicDeclarationSyntaxGenerator" assembly="%programfiles%\Sandcastle\ProductionTools\BuildComponents\SyntaxGenerators.dll" />
               <generator type="Microsoft.Ddue.Tools.CPlusPlusDeclarationSyntaxGenerator" assembly="%programfiles%\Sandcastle\ProductionTools\BuildComponents\SyntaxGenerators.dll" />
             </generators>
           </component>
           </then>
     </component>
     <!-- Copy in comments -->
     <component type="Microsoft.Ddue.Tools.CopyFromIndexComponent" assembly="%programfiles%\Sandcastle\ProductionTools\BuildComponents\BuildComponents.dll">
           <index name="comments" value="/doc/members/member" key="@name" cache="100">
             <data files="Documentation\*.xml" />
           </index>
           <copy name="comments" source="*" target="/document/comments" />
     </component>
     <!-- Copy in reflection data and comments for members -->
         <component type="Microsoft.Ddue.Tools.ForEachComponent" assembly="%programfiles%\Sandcastle\ProductionTools\BuildComponents\BuildComponents.dll">
           <variable expression="/document/reference/elements/element/@api" />
           <components>
             <component type="Microsoft.Ddue.Tools.CopyFromIndexComponent" assembly="%programfiles%\Sandcastle\ProductionTools\BuildComponents\BuildComponents.dll">
           <copy name="reflection" source="*[not(local-name()='elements')]" target="/document/reference/elements/element[@api=$key]" />
         </component>
             <component type="Microsoft.Ddue.Tools.CopyFromIndexComponent" assembly="%programfiles%\Sandcastle\ProductionTools\BuildComponents\BuildComponents.dll">
               <copy name="comments" source="summary" target="/document/reference/elements/element[@api=$key]" />
             </component>
           </components>
         </component>
     <!-- transform -->
         <component type="Microsoft.Ddue.Tools.TransformComponent" assembly="%programfiles%\Sandcastle\ProductionTools\BuildComponents\BuildComponents.dll">
           <transform file="%programfiles%\Sandcastle\Presentation\transforms\main_sandcastle.xsl" />
         </component>
     <!-- resolve shared content -->
         <component type="Microsoft.Ddue.Tools.SharedContentComponent" assembly="%programfiles%\Sandcastle\ProductionTools\BuildComponents\BuildComponents.dll">
           <content file="%programfiles%\Sandcastle\Presentation\content\shared_content.xml" />
           <content file="%programfiles%\Sandcastle\Presentation\content\reference_content.xml" />
     </component>
     <!-- resolve reference links -->
     <component type="Microsoft.Ddue.Tools.ResolveReferenceLinksComponent" assembly="%programfiles%\Sandcastle\ProductionTools\BuildComponents\BuildComponents.dll">
       <targets files="reflection.xml" type="local" />
     </component>
     <!-- save the result -->
         <component type="Microsoft.Ddue.Tools.SaveComponent" assembly="%programfiles%\Sandcastle\ProductionTools\BuildComponents\BuildComponents.dll">
           <save path="concat('Output\html\',/html/head/meta[@name='guid']/@content,'.htm')" indent="false" omit-xml-declaration="true" />
         </component>
       </components>
     </builder>
   </dduetools>
 </configuration>

 

Now we have to hook it up to MSBuild. Within the TFSProj.proj file, a new target was created called by an override to the Team Build AfterDrop target

 <PropertyGroup>
     <SandcastleDocumentDirectory>$(TEMP)\SandcastleDocument</SandcastleDocumentDirectory>
 </PropertyGroup>
 <Target Name="SandcastleDocument">    
     <CreateProperty Value="MyDocumentation">
       <Output TaskParameter="Value" PropertyName="SandcastleDocumentationName"/>
     </CreateProperty>
     <RemoveDir Condition="Exists('$(SandcastleDocumentDirectory)')" Directories="$(SandcastleDocumentDirectory)" ContinueOnError="false"/>
     <MakeDir Directories="$(SandcastleDocumentDirectory)" ContinueOnError="false" />
     <CreateItem Include="$(SolutionRoot)\Sandcastle\Aug 2006 CTP\Build.cmd">
       <Output ItemName="SandcastleBuildScript" TaskParameter="Include"/>
     </CreateItem>
     <Copy SourceFiles="@(SandcastleBuildScript)" DestinationFiles="@(SandcastleBuildScript -> '$(SandcastleDocumentDirectory)\Build.cmd')" ContinueOnError="false" />

 

     <CreateItem Include="$(SolutionRoot)\Sandcastle\Aug 2006 CTP\sandcastle.config">
       <Output ItemName="SandcastleConfiguration" TaskParameter="Include"/>
     </CreateItem>
     <Copy SourceFiles="@(SandcastleConfiguration)" DestinationFiles="@(SandcastleConfiguration -> '$(SandcastleDocumentDirectory)\sandcastle.config')" ContinueOnError="false" />

 

     <MakeDir Directories="$(SandcastleDocumentDirectory)\Documentation" ContinueOnError="false"/>
     <CreateItem Include="$(BinariesRoot)\x86\Debug\*.xml">
       <Output ItemName="CodeDocumentationFiles" TaskParameter="Include"/>
     </CreateItem>
     <Copy SourceFiles="@(CodeDocumentationFiles)" DestinationFiles="@(CodeDocumentationFiles -> '$(SandcastleDocumentDirectory)\Documentation\%(Filename)%(Extension)')" ContinueOnError="false"/>

 

     <Exec WorkingDirectory="$(SandcastleDocumentDirectory)" Command="Build.cmd &quot;$(BinariesRoot)\x86\Debug&quot; $(SandcastleDocumentationName)" ContinueOnError="false" />

 

     <CreateItem Include="$(SandcastleDocumentDirectory)\output\$(SandcastleDocumentationName).chm" ContinueOnError="false">
       <Output ItemName="SandcastleDocumentationFiles" TaskParameter="Include"/>
     </CreateItem>
     <MakeDir Condition="!Exists('$(BinariesRoot)\Documentation')" Directories="$(BinariesRoot)\Documentation" ContinueOnError="false" />
     <Copy SourceFiles="@(SandcastleDocumentationFiles)" DestinationFiles="@(SandcastleDocumentationFiles -> '$(BinariesRoot)\Documentation\%(RecursiveDir)%(Filename)%(Extension)')" ContinueOnError="false" />

 

     <OnError ExecuteTargets="OnBuildBreak;"/>
   </Target>