Pirin: .NET Solution Generation Tool

Introduction

There are a few deficiencies in Visual Studio 2005 that interfere with my everyday work as a software developer:

  1. The default solutions created by Visual Studio 2005 are a plain collection of projects. Although this is rather simple, it is not very useful. For example, there is no dedicated location for third-party, pre-built assemblies that are shared by some/all of the projects in the solution (no GAC, thank you). Also, there is no separation between production code and tests. I like the solution structure outlined by Jean-Paul Boodhoo. It supports solution independence (among other things) and makes a lot of sense to me as a whole. VS 2005 provides support for custom templates of projects, but I found that they were not flexible enough to support such a model.
  2. I want also to get bundles of projects: class library + NUnit class library (also, when I make a new class in the class library, I want an auto generated test fixture for it in the NUnit library), web service + web setup project, and so on.
  3. Another thing that bugs me, when it comes to VS projects, is that the name of the project is also the name of the default namespace for that project as well as its output. Because this is usually not the case with my projects (and quite likely with yours too, if you care about these kind of things), I have to rename a few things right after I have made a new one: the default namespace, the assembly output filename, and the namespace in the class that is generated automatically with the project (these are the steps for a .NET assembly).

These are the main reasons I came up with this tool—Pirin (pronounced [PIR-in]). It allows for a very simple, yet quite powerful generation of clusters of files and folders (in my case, these clusters happen to be files and folders part of a VS 2005 solution). Its application is not limited to Visual Studio projects only; it can be useful wherever a re-occurring similar structure of files/folder is necessary.

What It Does

This section will demonstrate the capabilities of Pirin without generating full-blown VS solutions. I will demonstrate that “production” functionality a bit later.

There are two downloads attached to this article. The first one (pirin_simple.zip) is used in this section. Look what you have as part of the tool:

For the purpose of this demonstration, I have created a folder, c:\pirin_demo in, which I will execute the demo; I also have extracted the tool inn the c:\pirin folder. This folder has two subfolders: “scripts” and “demo_folders”. The “scripts” folder contains the tool’s core implementation as well as customizations on top of that core (NAnt scripts). These are light reading even for novices in NAnt, so if you are interested in the gory details, I encourage you to fire up your favourite text editor and take a look at those files. Now, look at the folder named “demo_folders”.

The idea is to have a dedicated location on your hard drive where you store your project templates—the template store (for this demo, template store is c:\pirin\demo_folders). Under it, there are folders that represent the root of different project templates (in this case, I have one project template: dir_template_1). Under each project template root, there is a subtree of files and folders (the project template), which will get copied/transformed to the target folder. As you can see, in this demo there is one folder under the project template “dir_template_1” – “${environment;;get-user-name()}” and under it, one file named “${param_file_name}.txt.nant“. Focus first on the folder name. It might seem quite familiar to those acquainted with NAnt; it looks like a NAnt expression, just mistyped. Indeed, what I really wanted to name this folder was ${environment::get-user-name()}, but Windows does not allow the symbol “:” to be part of file/folder names (quite understandably). Therefore, I decided to substitute :: for ;; because it looks very close to what it really should be, and does not cause me any trouble (so far). So, in my processing of file and folder names, on the first pass, I replace all occurrences of “;;” with “::”. After that, I process the file/folder names as if they are expressions in a NAnt script. Still wondering what does this mean? Just run the tool and see the result:

nant /f:c:\pirin\scripts\pirin.demo.build dir_template_1
/D:param_file_name=readme /D:param_myname=Pirin_simple_demo

The result looks like this:

So, the first parameter to NAnt is the file that contains the script (in this case, pirin.demo.build), followed by the name of the project template and then by the definition of the NAnt properties that might be necessary to successfully expand the NAnt expressions embedded in file/folder names and files (more about the last one later)

In case you wonder where this mysterious environment::get-user-name()is defined: It is a standard function bundled with NAnt that gets the username of the user who launched the NAnt script. I assure you that you can very easily write your very own custom functions (in .NET) and call them in this manner.

Here is the file/folder structure in the directory where I executed the tool.

Now, turn your attention to the contents of the source file:

${param_myname} is a NAnt property that I define on the command line; ${datetime::now()} is again a standard NAnt function. The generated file’s contents looks like this:

How It Works

The idea behind this tool is quite simple. It consists of two basic components: path generator and file contents generator. As you will see, there is a common pattern behind these two.

Path generator

The path generator takes a subtree of folders (the tree template) and copies it to another location—the destination location. In the process, any NAnt-based expressions that are part of file/folder names are evaluated and replaced by their respective values.

As shown in the demo, the following mapping takes place.

Source file/folder name Target file/folder name
C:\pirin\demo_folders\dir_template_1 c:\pirin_demo
  \${environment;;get-user-name()}   \petar
    \${param_file_name}.txt.nant     \readme.txt

Contents generator

Files are in general copied from the project template to the target location without any processing. This rule has one exception—files with the “.nant” extension. These files are processed in a very similar way as the file/folder names described above: All NAnt expressions embedded in the files are evaluated and replaced by their respective values. The resulting files are stripped of the .nant extension and are copied to the target location.

More by Author

Must Read