Software Design Blog

Journey of Rinat Abdullin

Let's Integrate Our DSL Compiler Into the Build Process

This is the second article in “Capture business requirements with the Boo-based DSL” series. We are just getting started on the series and will talk about simple way of integrating our DSL compiler into the build process of Visual Studio. This approach would simplify learning custom DSLs and could save some head-ache for anyone, who does not want to bother with on-the-fly compilation in the very beginning.

Normally any DSL scripts implemented via Boo could be used in two distinct ways:

  • Scripts that are distributed in the raw form and get compiled into .NET at run-time.
  • Scripts that get compiled into .NET within the main build and are distributed as assemblies.

First approach is more suitable for large and complex solutions. that could require on-the-fly configuration without recompiling the solution. For example, by editing files manually or automatically getting the update from the central update server (file monitoring could be established to recompile the files once the change is detected).

Advantages of this approach are obvious; disadvantages are:

  • We have to deal with possible memory leaks here (scripts get compiled to in-memory assemblies and loaded into the AppDomain without any option of unloading).
  • Although there is Rhino.Dsl (micro-framework that helps to tame the Boo compiler), the complexity of logic being introduced to the system could be too much for the task (unnecessary complexity of the solution drops the efficiency of any future development and maintenance).
  • DSL scripts in this situation should be 100% issue-free, since it would be a nightmare to debug them.

Second approach is more suitable for small solutions that are easy to deploy and do not need real-time flexibility. It could also be used to develop and debug the actual scripts before deploying them to the real-time system.

One of the simplest ways to implement that is:

  • Put *.boo scripts into the same project that defines base classes, custom compiler steps, meta attributes and other customizations that compose the DSL.
  • Add post-build steps to the project file:

  • Invoke boo compiler with custom pipeline to generate compiled DSL .NET assembly

  • The previous step could be enough, but we can take it a little bit further - additionally use ILMerge to merge the DSL assembly back into the base assembly.

Here’s the sample snippet that can do this:

<Import Project="..\Resource\Build\Boo.Microsoft.Build.targets" />
<PropertyGroup>
  <ILMerge>..\Resource\Build\Ilmerge.exe</ILMerge>
  <BooAssembly>$(IntermediateOutputPath)_boo_$(TargetFileName)</BooAssembly>
  <CoreAssembly>$(IntermediateOutputPath)$(TargetFileName)</CoreAssembly>
</PropertyGroup>
<Target Name="AfterBuild">
  <Booc ToolPath="..\Resource\Boo\" 
    Verbosity="Verbose" 
    SourceDirectory="$(MSBuildProjectDirectory)" 
    TargetType="Library" 
    OutputAssembly="$(BooFilePath)" 
    References="@(MainAssembly)" 
    Pipeline="Request.Core.MyPipeline, Request.Core" 
  />
  <Exec Command="$(ILMerge) 
    /out:&quot;@(MainAssembly)&quot; 
    &quot;$(CoreAssembly)&quot; 
    &quot;$(BooAssembly)&quot;" 
  />
</Target>

NB: “Exec Command” xml node should be on one line, I’ve wrapped it here for the readability.

Boo.Microsoft.Build.targets is just an MSBuild file that hooks to the Boo.Microsoft.Build.Tasks.dll from the BooLangStudio distribution. It just have following statements:

  <UsingTask
    TaskName="Boo.Microsoft.Build.Tasks.Booc"
    AssemblyFile="Boo.Microsoft.Build.Tasks.dll"/>

  <UsingTask
    TaskName="Boo.Microsoft.Build.Tasks.ExecBoo"
    AssemblyFile="Boo.Microsoft.Build.Tasks.dll"/>

Some notes about the approach:

  • Boo scripts are compiled right in the main build process (it means “Ctrl+Shift+B” for the VS users and seamless support by the continuous integration)
  • Failures and warnings of your own compiler pipeline get out into the same “Error List” Our DSL compiler can report errors right into the VS IDE

  • Double-clicking on any compiler error will get you to the script

  • FXCop, NDepend or any other assembly-based analysis on the integration server will pick up the DSL code automatically
  • Later you will be able to re-use your Boo compiler customizations and DSL scripts in on-the-fly compilation.

PS: as always, check out the series page for all the links, referenced projects and updates.