Month: January 2009

rubicant – MsBuild Task added

Posted by – January 30, 2009

I just added an MsBuild task for rubicant tonight. I’m not very familiar with the command line options for MsBuild so some of these could probably be improved a bit, but until I know more I will leave them as they are.

The MsBuild Task is used to create tasks which will use MsBuild. This should help with people who need to build WPF applications (since MsBuild is the only way right now), or (fingers crossed) migrate from MsBuild.

The MsBuild Task is used as follows:

   1: MsBuildTask.new(:task_name => :dependencies) do |msb|
   2:   msb.no_autoresponse
   3:   msb.target
   4:   msb.logger
   5:   msb.distributed_logger
   6:   msb.console_logger_parameters
   7:   msb.validate
   8:   msb.verbosity
   9:   msb.no_console_logger
  10:   msb.max_cpu_count
  11:   msb.ignore_project_extensions
  12:   msb.file_logger
  13:   msb.distributed_file_logger
  14:   msb.node_reuse
  15:   msb.properties
  16:   msb.file_logger_parameters
  17:   msb.project_file
  18: end

Each of setters matches a command line option for MsBuild. For more information on what each one does, please view the documentation at http://msdn.microsoft.com/en-us/library/ms164311.aspx.

Values that can only be set (for example, /noautoresponse) can be set to true (set) or nil (unset).

Values, such as /property that take a list of option settings, must be passed a hash where the keys are the options and the values are the option values. For example, to set the output directory and Warning Level for a csproj compile, you could use:

   1: MsBuildTask.new(:compile) do |msb|
   2:   msb.properties = { :OutputDir => ".\\Output", :WarningLevel => 2 }
   3:   msb.project_file = ".\\Project1\\project.csproj"
   4: end

As of yet, I haven’t added an MsBuild helper to help create this function. I will probably get this done fairly shortly.

Changing mbunit helper to a task

Posted by – January 25, 2009

The mbunit function in rubicant has been changed from just a function to a complete task.

MbUnitTask

Behind the scenes, what MBUnitTask does is copies over the minimal necessary support files from an MBUnit directory and then runs the tests. It uses the MBUnit Console Runner and is only tested with MBUnit 2.4. It is used as follows:

   1: MbUnitTask.new(:task_name => :dependencies) do |mbunit|
   2:   mbunit.mbunit_dir = "/path/to/mbunit" 
   3:   mbunit.test_dir = "/path/to/testing/dir" 
   4:   mbunit.test_files = ["tests.dll"]
   5:   mbunit.assembly_path => FileList["/assemblies/to/include/*.dll"],
   6:   mbunit.report_folder => "sets /rf:",
   7:   mbunit.report_name_format => "sets /rnf:",
   8:   mbunit.report_type => "sets /rt:",
   9:   mbunit.show_reports => "sets /sr:",
  10:   mbunit.transform => "sets /tr:",
  11:   mbunit.filter_category => "sets /fc:",
  12:   mbunit.exclude_category => "sets /ec:",
  13:   mbunit.filter_author => "sets /fa:",
  14:   mbunit.filter_type => "sets /ft:",
  15:   mbunit.filter_namespace => "sets /fn:",
  16:   mbunit.verbose => "sets /v{optionvalue}",
  17:   mbunit.shadow_copy => "sets /sc:" }
  18: end

 

Mbunit helper

   1: mbunit(name, dependencies, mbunit_dir, test_dir, test_files, opts={}) 

Usage is:

   1: mbunit(:run_tests, [:compile_tests], "/path/to/mbunit/dlls",
   2:         FileList["/test/dlls/test.dll"],
   3:         {:working_dir => "/dir/to/run/tests/in",
   4:          :assembly_path => FileList["/assemblies/to/include/*.dll"],
   5:          :report_folder => "sets /rf:",
   6:          :report_name_format => "sets /rnf:",
   7:          :report_type => "sets /rt:",
   8:          :show_reports => "sets /sr:",
   9:          :transform => "sets /tr:",
  10:          :filter_category => "sets /fc:",
  11:          :exclude_category => "sets /ec:",
  12:          :filter_author => "sets /fa:",
  13:          :filter_type => "sets /ft:",
  14:          :filter_namespace => "sets /fn:",
  15:          :verbose => "sets /v{optionvalue}",
  16:          :shadow_copy => "sets /sc:" } 

Runs the tests in test_files in the test_dir with the dlls from mbunit_dir using the options in opts.

For more information on the command line arguments for the mbunit console runner, please visit: http://docs.mbunit.com/help/html/gettingstarted/MbUnitConsoleRunner.htm

rubicant update – Easy installation via gem

Posted by – January 25, 2009

Since I already had rubicant building as a gem, it made sense to make it available as a gem. I have moved the project to github and it is now located at: http://github.com/mendicantx/rubicant/

In addition, you can also install rubicant via gem:

   1: gem sources -a http://gems.github.com
   2: sudo gem install mendicantx-rubicant

You should only ever have to run the gem sources command once. This adds github to your sources of remote gems. From what I understand, there’s a ton of great projects there.

Now you don’t have an excuse to try it out!

Introducing Rubicant – Part 3 – The inner workings

Posted by – January 22, 2009

Don’t forget to check out Part 1 and Part 2.

Rubicant basically has 4 main parts, as mentioned in the first part of this series. They are as follows:

The CscTask

The CscTask is used for compiling C# files. It’s usage is like:

   1: CscTask.new(:task_name => :dependencies) do |csc|
   2:   csc.target = 'library'
   3:   csc.references = FileList["libs\*.dll"]
   4:   csc.out = "hello_world.exe"
   5:   csc.debug = "full"
   6:   csc.files = FileList["src/**/*.cs"]
   7: end

target: Any target that the csc compiler takes (exe, winexe, library, etc)

references: The list of assemblies that are referenced during compilation

out: The output filename

debug: if this is set(to anything), the compiler will be passed /debug:full to create debugging information

files: The list of files to be compiled

CscTask also has a shortcut that can be accessed as follows:

   1: csc(name, dependencies, opts)
   2:  
   3: ex:
   4: csc :name, [:dependencies], { :target => 'library',
   5:                               :references = FileList["libs\*.dll"],
   6:                               :out = "hello_world.exe",
   7:                               :debug = "full",
   8:                               :files = FileList["src/**/*.cs"] }

 

The MBUnit Helper

The MBUnit helper is used to run tests using mbunit v2. It is used as follows:

   1: mbunit(mbunit_dir, test_dir, test_files, opts={})
   2:  
   3: ex:
   4: mbunit("/path/to/mbunit/dlls",
   5:         FileList["/test/dlls/test.dll"],
   6:         {:working_dir => "/dir/to/run/tests/in",
   7:          :assembly_path => FileList["/assemblies/to/include/*.dll"],
   8:          :report_folder => "sets /rf:",
   9:          :report_name_format => "sets /rnf:",
  10:          :report_type => "sets /rt:",
  11:          :show_reports => "sets /sr:",
  12:          :transform => "sets /tr:",
  13:          :filter_category => "sets /fc:",
  14:          :exclude_category => "sets /ec:",
  15:          :filter_author => "sets /fa:",
  16:          :filter_type => "sets /ft:",
  17:          :filter_namespace => "sets /fn:",
  18:          :verbose => "sets /v{optionvalue}",
  19:          :shadow_copy => "sets /sc:" }

For a complete listing of the mbunit command line options, please visit http://docs.mbunit.com/help/html/gettingstarted/MbUnitConsoleRunner.htm.

This runs the tests in test_files in the test_dir with the dlls from mbunit_dir using the options in opts. Currently, this will physically copy the minimum required mbunit dlls and mbunit.cons.exe to the test dir, run the tests and then remove the files.

The CopyFiles Task

The copy files task will copy files from one directory to another, while maintaining the folder structure.

   1: CopyFiles.new(:name) do |cf|
   2:   cf.base_dir = "/some/dir"
   3:   cf.file_globs = ["**/somefiles/*.cs", **/otherfiles/*.cs"]
   4:   cf.target_dir = "/another/dir"
   5:   cf.excludes = ["AssemblyInfo.cs"]
   6: end

base_dir: The base directory to start copying files from

file_globs: The globs of files to be copied from base_dir

target_dir: The target directory to place the files in

excludes: (Optional) list of files to be excluded from the copy.

The Expand Template File Task

Usage is:

   1: expand_template(template, output_file)

This will read in the file specified by template and expand it out and write it to the location of output_file.

WARNING: I am fairly certain that the only reason this is working right now for me is because I use configatron, which is accessed as a singleton. It is entirely possible that variables declared in your build script will not be accessible during template expansion. I will be testing this in the near future, however.

Framework Selection

Currently, only the .Net 2.0 and .Net 3.5 frameworks are configured. .Net 3.5 is the selection by default. However, you can choose which framework you want to use by adding framework={framework} in the build line. For example:

   1: rake deploy_local framework=net20

This will run the deploy_local task in the default rakefile using the .Net 2.0 framework.

Valid framework values are:

net20 – .Net 2.0

net35 - .Net 3.5

These frameworks are discovered using the registry, so you shouldn’t have to put in the location of your csc.exe file.

What else?

Well, that’s about all it really does right now. The rest is just built in rake and ruby goodness. I would like to add msbuild support for those that are building WPF apps. I would also like to add some nunit support, as well as some support for other things like easy creation of assemblyinfo.cs files and possibly some deployment tasks for iis7. Who knows. We’ll see how I like things and if I really take to using this (I think I will).

Introducing rubicant – Part 2 – A sample script

Posted by – January 22, 2009

Update: There is also a Part 1 and a Part 3.

Alright, so here is a sample build script for rubicant. First you will have to get rubicant installed. To do that, you need to get the source from https://rubicant.googlecode.com/svn/trunk/. I believe the username is rubicant-read-only.

In the root directory type:

   1: E:\projects\rubicant>rake gem
   2: (in E:/projects/rubicant)
   3: c:/ruby/lib/ruby/gems/1.8/gems/rake-0.8.3/lib/rake/gempackagetask.rb:13:Warning:
   4:  Gem::manage_gems is deprecated and will be removed on or after March 2009.
   5: mkdir -p pkg
   6: WARNING:  no rubyforge_project specified
   7: WARNING:  RDoc will not be generated (has_rdoc == false)
   8: WARNING:  deprecated autorequire specified
   9:   Successfully built RubyGem
  10:   Name: Rubicant
  11:   Version: 0.0.1
  12:   File: Rubicant-0.0.1.gem
  13: mv Rubicant-0.0.1.gem pkg/Rubicant-0.0.1.gem
  14:  
  15: E:\projects\rubicant>

Now you’ve got the gem built, you just need to install it:

   1: E:\projects\rubicant>gem install pkg\Rubicant-0.0.1.gem
   2: Successfully installed Rubicant-0.0.1
   3: 1 gem installed
   4:  
   5: E:\projects\rubicant>

 

There. Now for a test application. Suppose we’ve got 3 projects that make up a console app. The UI, the Engine (I KNOW) and the Tests. They look like:

solution

For now, let’s ignore the internal workings and focus on building it.

The build script looks like this:

   1: require 'rubicant'
   2: require 'configatron'
   3: include Rubicant
   4:  
   5: BASE_DIR = File.expand_path(".")
   6: SRC_DIR = "#{BASE_DIR}/src"
   7: SRC_APP_DIR = "#{SRC_DIR}/app"
   8: SRC_TEST_DIR = "#{SRC_DIR}/test"
   9: CONFIG_DIR = "#{BASE_DIR}/config"
  10: BUILD_DIR = "#{BASE_DIR}/build"
  11: DEPLOY_DIR = "#{BASE_DIR}/deploy"
  12: TOOLS_DIR = "#{BASE_DIR}/tools"
  13: MBUNIT_DIR = "#{TOOLS_DIR}/mbunit"
  14: RHINO_DIR = "#{TOOLS_DIR}/rhino"
  15:  
  16: UI_SOURCES = FileList["#{SRC_APP_DIR}/Rubicant.Test.App.UI/**/*.cs"]
  17: ENGINE_SOURCES = FileList["#{SRC_APP_DIR}/Rubicant.Test.App.Engine/**/*.cs"]
  18: TEST_SOURCES = FileList["#{SRC_TEST_DIR}/**/*.cs"]
  19:  
  20: #### Build Initialization
  21: task :initialize_build do
  22:     rm_rf BUILD_DIR if File.exists?(BUILD_DIR)
  23:     mkdir_p BUILD_DIR
  24:     rm_rf DEPLOY_DIR if File.exists?(DEPLOY_DIR)
  25:     mkdir_p DEPLOY_DIR
  26: end
  27:  
  28: #### Loading Properties
  29: task :load_local_properties do
  30:     configatron.configure_from_yaml("local.properties.yml")
  31: end
  32:  
  33: task :load_test_properties do
  34:     configatron.configure_from_yaml("test.properties.yml")
  35: end
  36:  
  37: #### Deployment Targets
  38: task :deploy_local => [:load_local_properties, :deploy]
  39:  
  40: task :deploy_test => [:load_test_properties, :deploy]
  41:  
  42: task :deploy => [:test, :expand_app_config] do
  43:     cp FileList["#{BUILD_DIR}/{hello.exe,engine.dll}"], DEPLOY_DIR
  44: end
  45:  
  46:  
  47: #### Compile Targets
  48: csc :compile, [:initialize_build, :compile_engine], {:files => UI_SOURCES,
  49:                                                      :target => "exe",
  50:                                                      :out => "#{BUILD_DIR}/hello.exe",
  51:                                                      :references => FileList["#{BUILD_DIR}/*.dll"] }
  52:                 
  53: csc :compile_engine, [], {:files => ENGINE_SOURCES,
  54:                           :target => "library",
  55:                           :out => "#{BUILD_DIR}/engine.dll"}
  56:                 
  57: #### Testing Targets                
  58: task :test => [:compile_tests, :run_tests]
  59:  
  60: csc :compile_tests, [:compile], {:files => TEST_SOURCES,
  61:                                           :target => "library",
  62:                                           :out => "#{BUILD_DIR}/tests.dll",
  63:                                           :references => FileList["#{BUILD_DIR}/**/*.{dll,exe}", "#{RHINO_DIR}/*.dll", "#{MBUNIT_DIR}/*.dll"]}
  64:  
  65: task :run_tests do
  66:     mbunit(MBUNIT_DIR, BUILD_DIR, FileList["#{BUILD_DIR}/tests.dll"], {:assembly_path => FileList["#{BUILD_DIR}/engine.dll", "#{RHINO_DIR}/*.dll"].to_a})
  67: end
  68:  
  69:  
  70: #### App.config expansion
  71: task :expand_app_config do
  72:     expand_template "#{CONFIG_DIR}/App.Config.template", "#{DEPLOY_DIR}/hello.exe.config"
  73: end

The point of this build file is to be able to churn out two different builds based on two different sets of properties. This is based off of my builds that I do at work where we need to deploy and test in a test environment before going into production. It would be as simple as adding a production.properties.yml and a deploy_production target to simulate this fully in this script.

So basically, we call deploy_test (or deploy_local), which in turn loads the test properties, and then the deploy task.

Deploy calls the test task, which builds the app, builds the tests, runs the tests. If any tests fail, the build will fail here, making sure we don’t deploy any code that doesn’t pass tests.

Finally, we expand our app.config in a way suitable for the environment we’re going into.

Finally, run the executable to see that it worked!

   1: E:\projects\Rubicant.Test.App>deploy\hello.exe
   2: Hello, World! (Test Version)
   3:  
   4: E:\projects\Rubicant.Test.App>

 

You can download the source from http://blog.beigesunshine.com/wp-content/uploads/2009/01/rubicanttestapp.zip and try it out for yourself. The default rakefile goes to an exe and an assembly. You can also run both deploy_local and deploy_test and create a single exe file by using rake -f rakefile_single_exe.rb deploy_local.

Coming up, a post on some of the specifics of rubicant.

Introducing rubicant

Posted by – January 15, 2009

Update: Don’t forget to check out Part 2 and Part 3.

In previous posts I have mentioned that I think that there might be a better way to automate builds than NAnt. I also mentioned that I had looked at a few of them and been mostly underwhelmed. I’ve spent a large amount of time building a process for builds that works well for me and none of them seemed to erase my pains.

I also mentioned that I would be working on some items that may even drive some content to this blog, among other things. Today, I introduce the fruits of my labour.

Please note that this is NOT recommended for any sort of production environment. It is currently at best a way to try out Rake on .Net projects.

What is rubicant?

rubicant is my own personal set of tasks that sit on top of rake, a rubyish interpretation of make. I’ve used ruby for some scripting in the past, and also used rake in a rails app that I developed. I felt that the combination of both would alleviate some of the pains that I (and I’m pretty sure I’m not alone here) feel when I use NAnt.

I was admittedly scared to even try building my own set of tasks, and actually didn’t even know where to start. I kept rolling the idea around. One day I was going to start doing it now. Then the next I wasn’t. As a father of young children my spare time is very limited and very few and far between, especially for something that would demand so much of it.

And then the I started coming across others experimenting with exactly what I was talking about. First it was a post by Dave Laribee, as well as another post in his comments. And now as I read over them again, I’m seeing that there’s more people feeling like I do. I started to google and found some great posts by Jay Fields and to a lesser extent one by Martin Fowler.

Anyway, in my looking it seemed like no one had actually put out a reusable library that anyone could just pick up and automatically compile a project within 5 minutes.

And thus, with some much needed inspiration from the above sources, rubicant was born.

What isn’t rubicant?

Well written for one thing. Take a look at my tests and they’re enough to make someone blow a gasket :D (I kid, I LOVE his twitter rants) I started writing them thinking I knew what I was doing and let me clear something up for you. At the time, I had a greatly misguided view on BDD/Specification testing. Either way, it’s still got my code covered and has helped to drive out some of the design, especially when loading framework information.

Another thing it is not is a complete solution. I had 4 goals in functionality that I wanted. They were:

    1. Build C# code
    2. Test C# code (with MBUnit)
    3. Copy selected files in a meaningful hierarchy
    4. Template expansion using configatron to allow for easy configuration swapping.

Why rubicant?

First off, automated builds are the only thing that I feel I truly have a grasp on. I feel comfortable enough in myself and my process that I could come up with something useful. I don’t think of myself as a guru, but I do know that I have a set of build scripts at work that are robust and involved while trying to stay (somewhat simple). In other words, I don’t consider myself a guru, but I do feel I have a grasp on the subject.

I was also feeling too much pain with NAnt. As well, I didn’t feel that any of the other solutions out there really felt like what I was looking for. I was also looking for a learning project in ruby. I wanted to try driving out a design via testing using ruby. A lot of things. Either way, I felt like I wanted to do something for myself, but that also might provide some sort of benefit to the community, even if it just ended up as a failed attempt. In a sentence: I wanted to try and provide SOMETHING back to a community that I feel has given me so much.

Even if this ends in failure, it’s been exactly the experience that I’ve wanted thus far, and though I’m sure I’m in for some rough waters ahead, I think that it will continue to drive me to learn, improve and better myself. Okay, so there is definitely some selfishness there, but hopefully you can forgive me in the name of Continuous Improvement, even if it’s my own.

How do I get it? How do I use it?

Well, if you want to get it, you can go to the project site on google code and download the source. There are instructions there on how to install it. As for how to use it… stay tuned for a sample project and build file. Hopefully very soon.

 

Update: Don’t forget to check out Part 2 and Part 3.