With the release of Iron Ruby we are no more tide up with the xml based build script like NAnt and MSBuild, we can now use Rake with Iron Ruby to build our .NET based projects. In this post, I will show you a very basic build script in rake that will integrate StyleCop, Simian, FxCop, MSpec and NCover. I will use the same fund transfer project that I used in my previous post.
Before moving to the build script, let me give a brief description of the above tools:
- StyleCop: It works on source code and ensures that all of rules that you have defined earlier has been meet. These rules can include ode structure, naming Convention, documentation rules etc, you can even create your own custom rules if you want. It has been recently open sourced by Microsoft and can be download from http://stylecop.codeplex.com.
- Simian is another excellent tool which also works on source code. It detects the duplicate codes among the given source code files. Though it is a commercial product but it is free to use in non commercial or open source projects. It supports a lot of languages and you can download it from http://www.redhillconsulting.com.au/products/simian.
- FxCop is one of the tool that is available from the early days of .NET, similar to StyleCop but it enforce the rules on compiled outputs rather than source codes. The latest version of FxCop is included in the Windows 7 SDK which you can download from the Microsoft Download Center. In this post, I will be using the last standalone version 1.36.
- MSpec is my BDD Style Test Framework of choice. Currently there is no official binary version but you can download it and compile it yourself.
- NCover is my another favorite tool which reports the code coverage. Usually it is used with the test runner to capture the coverage. In this post I will be using the community edition of NCover which is free. You can download it from http://www.ncover.com/download/current.
Now we know the basics of the these tools, lets define the steps, since both StyleCop and Simian works on source codes we will invoke these before compiling our source codes and rest of the steps after the compilation:
- Run StyleCop
- Run Simian
- Compile
- Run FxCop
- Run Test And Coverage.
- Copy the build artifacts in the Drop location.
This is the skeleton of the rake script for the above steps:
task :style_cop do
puts "Running StyleCop"
end
task :simian do
puts "Running Simian"
end
task :compile do
puts "Compiling projects"
end
task :fx_cop do
puts "Running FxCop"
end
task :test_and_coverage do
puts "Running Test and capturing the code coverage"
end
task :drop do
puts "Preparing the build artifacts"
end
task :default => [:style_cop, :simian, :compile, :fx_cop, :test_and_coverage, :drop] do
puts "Done"
end
Consider task as MSBuild Target and it has an associated name (ruby symbol) denoted as :name which is same as MSBuild Target name. To make a task depended on other tasks you have to specify the task names in square brackets like the last task of in above script. To invoke one or more specific tasks you have to pass the task name as argument, for multiple tasks use space as separator. For example:
rake simian fx_cop # The rake command by default executes rakefile.rb
will show output:
Running Simian
Running FxCop
If none of the task is passed, it will execute the default task.
Now, lets add the codes in the above skeleton, but before that lets see the directory structure of the Fund Transfer Project:

Though we have six main tasks but there few more things that we have to add. First the initialization
MSBUILD = File.join(ENV["windir"] || ENV["WINDIR"], "Microsoft.NET", "Framework", "v3.5", "msbuild.exe")
CONFIG = ENV["config"] || ENV["CONFIG"] || "Debug"
CURRENT_PATH = File.dirname(__FILE__)
ARTIFACT_PATH = File.join(CURRENT_PATH, "Artifacts")
REPORT_PATH = File.join(ARTIFACT_PATH, "Reports")
REFERENCES_PATH = File.join(CURRENT_PATH, "References")
TOOLS_PATH = File.join(CURRENT_PATH, "Tools")
TEST_SUFFIX = "Specs"
projects = []
app_projects = []
app_files = []
app_dependency_files = []
test_projects = []
test_files = []
test_dependency_files = []
referenced_files = []
desc "Initilizing build"
task :init do
# This will create the Artifacts directory if it does not exist
FileUtils.mkdir_p ARTIFACT_PATH
# Delete the existing files/folders of the Artifacts directory
Dir.foreach(ARTIFACT_PATH) do | entry |
FileUtils.remove_entry(File.join(ARTIFACT_PATH, entry)) if (entry != ".") && (entry != "..")
end
# Create the Report folder
FileUtils.mkdir_p REPORT_PATH
# Get all the CSharp Project files that exists under the current path
projects = Dir.glob(File.join(CURRENT_PATH, "/**/*.csproj"))
# Now we have to indentify the Test/Spec project and the regular project
projects.each do | project |
project_name = File.basename(project, ".csproj")
# If Project has the special suffix we will treat it as Test/Spec Project
# otherwise it is a regular project
if /#{TEST_SUFFIX}$/.match(project_name)
test_projects << project
elsif
app_projects << project
end
end
end
In the above we are first declaring few constants(please note that in Ruby Constants are started with capital letters) and few variables which we are going to use in the tasks. The File.Join is same as .NET Path.Combine. The ENV[YOUR_KEY] is used get the value of Environment variables. In the init task we are making sure the Artifacts and its subfolder Reports exists, next we are scanning all the projects and storing it in the two array’s for later usages. By default, the script assumes that the MSpec projects ends with “Specs”. Next, we will add one more helper task which will clear the build outputs:
desc "Cleaning up outputs"
task :clean do
# Iterate all the project and pass to MSBuild for cleaning up the build outputs
projects.each do | project |
sh "\"#{MSBUILD}\" \"#{project}\" /p:Configuration=#{CONFIG} /t:Clean /tv:3.5"
end
end
The sh stands for shell, we are using it to execute the MSBuild.exe and passing the required parameters.
Now, lets modify the default task’s dependencies so that the above two tasks are executed prior the others:
desc "Default"
task :default => [:init, :clean, :style_cop, :simian, :compile, :fx_cop, :test_and_coverage, :drop] do
puts "Build Completed."
end
Among the tasks most of the tasks are invoking external processes, only the style_cop task requires bit of interaction with the clr types. Although StyleCop comes with MSBuild task but as we are not running in MSBuild environment, we cannot use it directly, instead we have to call the StyleCop directly from our rake script and this is the true power of Iron Ruby, we can easily call any clr type and vice versa. The following shows the code which runs the StyleCop analysis:
desc "Running StyleCop"
task :style_cop do
# We will need the reference of StyleCop
require File.join(TOOLS_PATH, "stylecop", "Microsoft.StyleCop.dll")
# Create a List<StyleCopProject> to store the regular projects
style_cop_projects = System::Collections::Generic::List[Microsoft::StyleCop::CodeProject].new
# Build the File path of StyleCop Report
report_file = File.join(REPORT_PATH, "StyleCop.xml")
# Create a StyleCop console to apply the StyleCop rules
style_cop_console = Microsoft::StyleCop::StyleCopConsole.new(nil, false, report_file, nil, true)
# Create a StyleCop Configuration
style_cop_configuration = Microsoft::StyleCop::Configuration.new(nil)
# We will only invoke StyleCop for the regular project, not for the Test/Specs.
app_projects.each do | project |
style_cop_project = Microsoft::StyleCop::CodeProject.new(project.hash, project, style_cop_configuration)
# Add all the .cs files that resides in this project
source_files = Dir.glob(File.join(File.dirname(project), "/**/*.cs"))
source_files.each do | source_file |
style_cop_console.core.environment.add_source_code(style_cop_project, source_file, nil)
end
style_cop_projects.add(style_cop_project)
end
# Start applying the rules
style_cop_console.start(style_cop_projects, true)
# Now release all the associated resources of StyleCop
style_cop_console.dispose()
end
The next two tasks are simple external program execution, it just prepares the required parameters and invokes the program:
desc "Running Simian"
task :simian do
simian_path = File.join(TOOLS_PATH, "simian")
simian_exe = File.join(simian_path, "simian-2.2.24.exe")
simian_report = File.join(REPORT_PATH, "Simian.xml")
app_projects_paths = app_projects.map { | project | "\"" + File.dirname(project) + "\""}.join(" ")
# Copy the xsl file in the report folder so that we can view the xml as html
FileUtils.copy(File.join(simian_path, "Simian.xsl"), REPORT_PATH)
sh "\"#{simian_exe}\" #{app_projects_paths} -formatter=xml:\"#{simian_report}\" -failOnDuplication- -reportDuplicateText+ -includes=\"**/*.cs\""
end
desc "Compiling source"
task :compile do
projects.each do | project |
sh "\"#{MSBUILD}\" \"#{project}\" /p:Configuration=#{CONFIG} /t:Build /tv:3.5"
end
end
Now we add one last helper task, this will ensure that all the project outputs and referenced components are copied into the build Artifacts folder, so that we can use the folder as Working Directory to run the FxCop and MSpec specifications.
desc "Copy compiled files"
task :copy_files do
app_projects.each do | project |
project_directory = File.dirname(project)
project_name = File.basename(project, ".csproj")
project_outputs = Dir.glob(File.join(File.join(project_directory, "bin", CONFIG), "/**/*.*"))
project_outputs.each do | file |
base_name = File.basename(file)
if File.basename(base_name, File.extname(file)).eql?(project_name)
app_files << base_name
else
app_dependency_files << base_name
end
FileUtils.copy(file, ARTIFACT_PATH);
end
end
test_projects.each do | project |
project_directory = File.dirname(project)
project_name = File.basename(project, ".csproj")
project_outputs = Dir.glob(File.join(File.join(project_directory, "bin", CONFIG), "/**/*.*"))
project_outputs.each do | file |
base_name = File.basename(file)
if File.basename(base_name, File.extname(file)).eql?(project_name)
test_files << base_name
else
test_dependency_files << base_name if !app_files.include?(base_name) && !app_dependency_files.include?(base_name)
end
FileUtils.copy(file, ARTIFACT_PATH);
end
end
Dir.glob(File.join(REFERENCES_PATH, "/**/*.*")).each do | file |
referenced_files << File.basename(file)
FileUtils.copy(file, ARTIFACT_PATH);
end
end
Depending upon the configuration (usually Debug/Release) we are copying the build outputs from the bin directory of each project and putting it into the Artifacts folder, when copying we are also populating few file list variables so that we can later use it in the FxCop and MSpec tasks. This tasks will be invoked after the compiling the projects. So we will again modify the dependencies of the default task.
desc "Default"
task :default => [:init, :clean, :style_cop, :simian, :compile, :copy_files, :fx_cop, :test_and_coverage, :drop] do
puts "Build Completed."
end
The next two tasks are also invoking the external processes and building the required parameters:
desc "Running FxCop"
task :fx_cop do
fxcop_path = File.join(TOOLS_PATH, "fxcop")
fxcop_exe = File.join(fxcop_path, "FxCopCmd.exe")
fxcop_report = File.join(REPORT_PATH, "FxCop.xml")
dlls = "/f:" + app_files.select{ |file| File.extname(file).eql?(".dll")}.map { | dll | "\"" + File.join(ARTIFACT_PATH, dll) + "\""}.join(" /f:")
FileUtils.copy(File.join(fxcop_path, "Xml", "FxCopReport.xsl"), REPORT_PATH)
sh "\"#{fxcop_exe}\" #{dlls} /d:\"#{ARTIFACT_PATH}\" /o:\"#{fxcop_report}\" /oxsl:\"FxCopReport.xsl\" /to:0 /fo /gac /igc /q"
end
desc "Running tests"
task :test_and_coverage do
test_runner_exe = File.join(TOOLS_PATH, "mspec", "mspec.exe")
app_dlls = app_files.select{ |file| File.extname(file).eql?(".dll")}.map { | dll | File.basename(File.basename(dll), File.extname(dll)) }.join(";")
test_dlls = test_files.select{ |file| File.extname(file).eql?(".dll")}.map { | dll | File.join(ARTIFACT_PATH, dll) }.join(" ")
test_runner_argument = "\"#{test_runner_exe}\" --html \"#{REPORT_PATH}\" \"#{test_dlls}\""
exclude_attributes = "System.Runtime.CompilerServices.CompilerGeneratedAttribute;System.CodeDom.Compiler.GeneratedCodeAttribute;System.Diagnostics.DebuggerNonUserCodeAttribute"
ncover_coverage = File.join(REPORT_PATH, "NCover.Console.xml")
ncover_path = File.join(TOOLS_PATH, "ncover")
ncover_console_exe = File.join(ncover_path, "NCover.Console.exe")
ncover_console_argument = "#{test_runner_argument} //a \"#{app_dlls}\" //x \"#{ncover_coverage}\" //ea \"#{exclude_attributes}\" //w \"#{ARTIFACT_PATH}\" //q"
FileUtils.copy(File.join(ncover_path, "Coverage.xsl"), REPORT_PATH)
sh "RegSvr32 \"#{File.join(ncover_path, "CoverLib.dll")}\" /s"
sh "\"#{ncover_console_exe}\" #{ncover_console_argument}"
sh "RegSvr32 \"#{File.join(ncover_path, "CoverLib.dll")}\" -u /s"
ncover_explorer_exe = File.join(ncover_path, "NCoverExplorer.Console.exe")
ncover_report = File.join(REPORT_PATH, "NCoverExplorer.Console.xml")
FileUtils.copy(File.join(ncover_path, "CoverageReport.xsl"), REPORT_PATH)
sh "\"#{ncover_explorer_exe}\" \"#{ncover_coverage}\" /r:ModuleClassFunctionSummary /x:\"#{ncover_report}\" /q"
end
And in the last task we will do some cleanup so that the artifacts directory only contains the application dll/pdb/xml docs and all the xml/html reports of the above tools.
desc "Preparing drop"
task :drop do
app_dependency_files.concat(test_files).concat(test_dependency_files).concat(referenced_files).uniq().each do | file |
FileUtils.remove_file(File.join(ARTIFACT_PATH, file), true)
end
end
If you open the Artifacts and the Reports folder after running the rake scripts, you will see:

As mentioned that it is a very basic rake example which you can use for building relatively simple applications as it does not have any customization support (though you can always modify the source code). But the good news is there is already rake task library for the .NET applications called Albacore which I highly recommend you to check.
That’s it for today. You can download the complete source code of this post from the following link.
Download: RakeIntegration.zip