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.

Pirin: .NET Solution Generation Tool

What It's Good For

In this section, I will demonstrate how I use Pirin in my everyday work (use the second attachment—pirin_full.zip).

In the company where I am currently employed (Industria), we have rather standard scenarios when it comes to projects: Develop the core functionality as a class library, then expose it through a web service or a command-line application, and finally create a deployment project for that web service or commandline application. This methodology is driven by the idea to have the core functionality nicely isolated so we can test it and reuse it easily. So, let me demonstrate how I am able to generate all of these components in such a way that I can get right to business without fooling around with settings.

Delete the folders used for the previous demo (c:\pirin and c:\pirin_demo) and re-create them. For this demo, I will look at the second package attached to this article. That is the one that I am really using in my everyday work; it contains everything you need to use it (except NAnt itself). The core of Pirin is the same as in the previous demo, but the custom script based on that is a bit more complex.

The first command that I issue is:

nant /f:c:\pirin\scripts\pirin.custom.build initial
-D:param_root_namespace=Pirin.Demo

It produces the initial structure for the solution, which in this case is a subset of the tree structure Jean-Paul Boodhoo describes.

[demo2-dest-after-initial.jpg]

In this step, there is no NAnt-ish processing; it is pretty much a plain, dumb copy-paste process (except for the rootnamespace.txt file, but I will not go into that; it is just a helper file for Pirin, not really used by the .NET project). Next, I will add a class library named Core:

nant /f:c:\pirin\scripts\pirin.custom.build class_library
-D:param_project_name=Core

This generates a class library in c:\pirin_demo\src\app\Core and an accompanying NUnit test class library named Core.Test located under c:\pirin_demo\src\test\Core. The Core library references the log4net assembly in the solution's libs folder and Core.Test references Core, NUnit and Rhino.Mocks.

Next, I want to generate a new class named "DemoClass":

nant /f:c:\pirin\scripts\pirin.custom.build class
-D:param_class_name=DemoClass -D:param_project_name=Core

It generates two files. The first one is Test_DemoClass.cs under c:\pirin_demo\src\test\Core; it has the following contents:

[demo2-dest-class-test.jpg]

and the other file is named DemoClass.cs in c:\pirin_demo\src\app\Core. It has the following contents:

[demo2-dest-class-impl.jpg]

At this point, you have everything ready to start coding the test and its class without having to deal with setting up things for the projects. I will not stop your attention on the development of the class and instead will pretend that it is time to expose its functionality through a command line application:

nant /f:c:\pirin\scripts\pirin.custom.build console_app
-D:param_project_name=ConsoleApp

This command generates a commandline application and an installer project that takes care of that commandline app. Here is the complete solution, as seen in Solution Explorer in VS 2005:

[demo2-dest-complete-solution.jpg]

Final Words

I would like to mention a few more things about Pirin:

  1. In its current form Pirin WILL NOT overwrite files.
  2. It is quite flexible, easy to extend, and the only information it really needs is the location of the template store.
  3. This is a lightweight code generation tool. It is simpler than full-featured code generators such CodeSmith, for example, but for many tasks it is powerful enough. It is available on any platform that NAnt runs on and is free.
  4. I know what you are thinking: "This guy expects me to type these long lines that I will never remember? Yeah, right ... ". Fear not! I used here "the long version" to demonstrate what the actual calls to NAnt are. I have a file named pirin.bat in the scripts folder; it nicely wraps this whole functionality and has help to remind you of what is what for each type of project.

Similar Applications

Tree Surgeon is another application that is aimed at generating project trees. To be honest, I found it right after I was done with the main functionality in Pirin, so I did not pay too much attention to it. Had I found it before I decided to write Pirin, I might have just switched to using the project tree it generates and used the readily available program. Anyway, I have very lightly surveyed that program but I found it to be less flexible than my solution. If you do not think so, I would like to hear your opinion.

Known Issues

  1. Because paths are evaluated for every file and folder, putting expressions in folder names that evaluate to different things every time will cause a mess you will not like. Will fix this on-demand (=the first time I or anybody else needs it).
  2. Pirin may interrupt the generation process halfway through because it refuses to overwrite a file that already exists. It will not clean up the files it has already generated as part of that last run (needs to be more transactional). Will fix this shortly.

Upcoming Features

  1. Add generation of NAnt build file for the solution


Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Protecting business operations means shifting the priorities around availability from disaster recovery to business continuity. Enterprises are shifting their focus from recovery from a disaster to preventing the disaster in the first place. With this change in mindset, disaster recovery is no longer the first line of defense; the organizations with a smarter business continuity practice are less impacted when disasters strike. This SmartSelect will provide insight to help guide your enterprise toward better …

  • Live Event Date: August 14, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT Data protection has long been considered "overhead" by many organizations in the past, many chalking it up to an insurance policy or an extended warranty you may never use. The realities of today make data protection a must-have, as we live in a data driven society. The digital assets we create, share, and collaborate with others on must be managed and protected for many purposes. Check out this upcoming eSeminar and join eVault Chief Technology …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds