Click here to Skip to main content
Click here to Skip to main content

Genuilder - Build Time Development Framework

, 29 Nov 2011 Ms-PL
Rate this:
Please Sign up or sign in to vote.
Generate, verify, modify code files automatically during the build process of your projects.

Table of contents

Introduction

This article has almost the same content as Genuilder's documentation. But CodeProject members are not developers like other... We are curious, we hack, combine, create, criticize ideas in order to find better solutions, for ourselves and for others. That's why I'll also talk about the implementation details of Genuilder at the end of this article.

In one sentence, Genuilder is a Build time development framework... Remember, it took me a long time to find how to describe Genuilder in one sentence. It means that you will be able to run your own code when developers build your project, emit error messages, generate code on the fly (for example, it is how WCF RIA services works), and more.

...or you can just use the built-in features, like AppConfig compile time verification, or async contract generators for WCF.

Installation

Go to genuilder.codeplex.com and download the Genuilder Visual Studio extension.

Run it.

GenuilderInstall.png

Restart Visual Studio, you should then see the new project item.

ProjectTemplate.png

Upgrading Genuilder

You have to uninstall the previous version of Genuilder in the Extension Manager (in Visual Studio "Tools/Extension manager").

UnInstall.png

Common features

Common features are accessible through the CommonFeature enumeration in your Genuilder project.

CommonFeaturesEnum.png

You need to run your Genuilder project each time you modify it to install or remove features from your projects.

AppConfigVerifier

AppConfigVerifier is an integration in Genuilder of this project. It scans all 'type=".*"' in the config file and checks if the type is referenced in the project. The documentation can be found on CodeProject. Here is a quick example, where in the config file you misspelled the MembershipProvider type:

AppConfigVerifier-Config.jpg

Genuilder will find the error at compile time:

AppConfigVerifier-Error.jpg

You can exclude types or assemblies from the verification, just check this article.

AppSettingsGenerator

This feature comes from one of my articles on CodeProject. With it you can access your AppSettings and ConnectionStrings in a strongly typed way. For example, the AppConfig:

AppSettingsGenerator-Config.jpg

Auto completion on AppSettings:

AppSettingsGenerator-AppSettings.jpg

Auto completion on ConnectionStrings:

AppSettingsGenerator-ConnectionStrings.jpg

EnvironmentFile

EnvironmentFile is just a pre-processor for your config file. For example, you have this app.config with a token:

environmentfile1.jpg

You also have this EnvironmentFile.env file which is just a key=value pair list delimited by line breaks:

environmentfile2.jpg

After compilation, the output config file (for example, MyProject.exe.config) will be:

environmentfile3.jpg

This is particularly useful when developers in a team do not have the same development environment, they don't need to checkout their own version of the app.config. (EnvironmentFile should be unique for each developer and should not be in your source control.)

If one of your fellow developers changes the config file like that:

environmentfile4.jpg

After the next Get latest version, the project will not compile because the new token is not set in EnvironmentFile.env:

environmentfile5.jpg

InterfaceExtractor

InterfaceExtractor is a code generator which extracts an interface from public members of classes and structs. This feature is really useful when you want to create static or dynamic proxies around a service or a controller for authentication or logging for example. Here is some other cool things to do with an interface. For example, this class:

InterfaceExtractor-Class.jpg

will create extract this interface:

InterfaceExtractor-Interface.jpg

Note that the parameter of ExtractInterface is optional, the default interface name is IClassName. Also, you don't have to implement the interface on your class.

Warning: [ExtractInterface(typeof(SomeNamespace.Interface))] is currently not allowed, you cannot specify the namespace of the extracted interface... not a big deal to add though.

PropertiesExtractor

Like InterfaceExtractor, PropertiesExtractor is a class generator which generates a static class to access property names in a strongly typed way. For example, this class implements INotifyPropertyChanged and does not hard code the property name with a string:

PropertiesExtractor-Class.jpg

Because PropertiesExtractor will generate this other class:

PropertiesExtractor-Properties.jpg

This way if you refactor the property name, the code will break at compile time instead of during runtime.

As with InterfaceExtractor, the parameter of the ExtractProperties is optional, the default class name is ClassNameProperties.

Warning: [ExtractProperties(typeof(SomeNamespace.MyClassProperties))] is currently not allowed, you cannot specify the namespace of the extracted class properties... not a big deal to add though.

StrongConfigGenerator

This feature generates a class to access your configuration file in a strongly typed way. If you remove a section from your config file and somewhere your code has a dependency on it, it will break at compile time.

For example, for this config file:

StrongConfigGenerator-Config.jpg

You will be able to access sections like this:

StrongConfigGenerator-Class.jpg

Extensions

Installing an extension in your project

An article has been written on CodeProject to introduce the Extensibility framework of Genuilder, but I will refresh your memory. To install an extension, you need to use ExtensibilityFeature, add extensions, and install it on the project. In this example, you install AsyncServiceContractsExtension on the file Program.cs of every project.

Using.png

Built-in Extensions

AsyncServiceContractsExtension

This extension creates a ServiceContract given a synchronous ServiceContract, useful for Silverlight projects. Given:

ICustomerService.png

Will generate:

ICustomerServiceAsync.png

An example is given here.

StronglyTypedReflectionExtension

It generates classes to access a type's member in a strongly typed way. Given:

Class1.png

It will generate a class to access PropertyInfo, MehtodInfo, EventInfo this way:

Class1Type.png

ProxyGeneratorExtension

The goal and usage of this IExtension is explained in this CodeProject article.

Custom Extensions

Object model

To create your own extension, you have to implement IExtension, here is the object model of Genuilder.Extensibility:

GenItems represents code files of your project, you can use ExecutionContext.CreateChild(string name) and GenItem.CreateChild(string name) to create new ones. When you use GenItem.CreateChild, the child will be deleted when the parent is deleted.

MSBuild logging

You can easily send warning, error, and info messages to Visual Studio by using ExecutionContext.Logger and GenItem.Logger. For example, in this example, I output an error on Program.cs line 5 column 6 in Visual Studio.

MyExtension.png

Once installed on my project, here is what happens at compile time:

Error.png

Debugging an Extension

You can easily debug your extensions by calling the Project.Build() method on your project.

ExtensionDebugging.png

In this case, you will be able to break in the MyExtension class to see what is going on during the build.

Easy code parsing and generation

Genuilder has a built-in object extension to easily parse CodeItems with NRefactory.

CompilationUnitExtension.png

You can get it this way:

var compilationExtension = genItem.GetExtension<CompilationUnitExtension>()

Then you can use the CompilationUnit property to walk through the Abstract Syntax Tree (AST). Or, more easily, use the Visitor pattern by implementing AbstractASTVisitor, overriding methods called during the visit of the tree, then calling CompilationUnit.Visit(visitor,null);. Take a look at the code source of ProxyGeneratorExtension.

In this example, you'll see how to use the CodeWriter class to generate readable code.

CodeWriter.png

Resharper

Currently, the intellisense of Resharper doesn't work on generated classes. But the code will compile.

Under the hood: All is feature

In Genuilder philosophy, all is feature and features are... Chunk of MSBuild XML in your project, nothing more, nothing else. And there is actually two types of features.

Under the hood, a CommonFeature is just a TargetFeature, as you can see in the implementation of InstallCommonFeature.

public void InstallCommonFeature(CommonFeature feature)
{
    InstallFeature(new TargetFeature(
       GetFullPath(_CommonFeature.GetFileName(feature), feature)));
}

For example, when you install a CommonFeature and an IExtension in your project:

foreach(var project in Projects.InSubDirectories("../..").ExceptForThisAssembly())
{
    var ex = new ExtensibilityFeature();
    ex.AddExtension(new MyExtension());
    project.InstallFeature(ex);
    project.InstallCommonFeature(CommonFeature.AppConfigVerifier);
    project.Save();
}

You will see in the csproj file the following XML :

Red elements are the delimiters of Genuilder's sections. Genuilder guarantees that everything it adds to your csproj is between these two elements.

Blue is the extensibility feature's section.

Cyan is your extension section, composed in three parts delimited by pipe '|' parts:

  • The full class name of your extension
  • The assembly of your extension
  • The DataContract serialization of your extension

Orange we ask to not use the Visual Studio compiler. Without it, auto completion on generated classes is buggy.

Green is the CommonFeature's section, as you can see it only includes an import to a target file.

A feature is really easy to implement, I use the MSBuild framework along with some of my own Extension Methods to modify the csproj file easily.

You can use the msBuildData parameter to add things just after the Genuilder's section delimiter; for example, this is how TargetFeature is implemented:

public override void LoadFeature(Project project, MSBuildData msBuildData)
{
    var importPath = PathUtil.RelativePathTo(
        new FileInfo(project.Path).Directory.FullName, 
        new FileInfo(_Path).FullName);
    msBuildData.ProjectRootElement.AddImport(importPath).After(
        msBuildData.StartImportGenuilder);
}

MSBuild integration of Extensions

As you can see in the Blue section, the Extensibility feature imports Genuilder.Extensibility.targets.

It just inserts its own Msbuild Task during compilation, and during cleaning.

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
    <UsingTask TaskName="Genuilder.Extensibility.MSBuild.ExtensibilityTask" 
           AssemblyFile="Genuilder.Extensibility.dll"/>
    <UsingTask TaskName="Genuilder.Extensibility.MSBuild.ExtensibilityCleanTask" 
           AssemblyFile="Genuilder.Extensibility.dll"/>

    <PropertyGroup>
        <CompileDependsOn>
            GenuilderExtensibility;
            $(CompileDependsOn)
        </CompileDependsOn>
    </PropertyGroup>
    <PropertyGroup>
        <CleanDependsOn>
            GenuilderExtensibilityClean;
            $(CleanDependsOn)
        </CleanDependsOn>
    </PropertyGroup>

    <Target Name="GenuilderExtensibility">
        <Genuilder.Extensibility.MSBuild.ExtensibilityTask
            CompileIn="@(Compile)"
            Extensions="@(GenuilderExtension)"
            OutputPath="$(IntermediateOutputPath)">
            <Output TaskParameter="CompileOut"
            ItemName="Compile"></Output>
        </Genuilder.Extensibility.MSBuild.ExtensibilityTask>
    </Target>
    <Target Name="GenuilderExtensibilityClean">
        <Genuilder.Extensibility.MSBuild.ExtensibilityCleanTask
            OutputPath="$(IntermediateOutputPath)" />
    </Target>
</Project>

The implementation of this task has been kept as small as possible, because it was really hard to test. The core logic of Genuilder.Extensibility is in the GenEngine.

I'll explain some lines of the ExtensibilityTask.Execute method.

First GenEngine needs an IGenLogger to add logging capability to IExtension and GenItem. ExtensibilityTask just provides an implementation of this interface for MSBuild logging, the MsBuildLogger.

That's what we find in the implementation of ExtensibilityTask.

GenEngine engine = new GenEngine();
engine.Logger = new MSBuildLogger(this.Log);

Then GenEngine needs your extensions.

engine.Extensions.AddRange(GetExtensions());

GetExtensions loads assemblies of every IExtension, and deserializes every extension. The problem is that these extensions were loaded in the Visual Studio process when compiling.

The implication is that when you try to debug an IExtension and recompile it, you got an error like this one:

Unable to copy file reference.dll to bin/reference.dll. 
  The process cannot access the file reference.dll because it is being used by another process

The fix is simple, I just need to load my ExtensibilityTask in a separate AppDomain, and unload it when the task is executed. That's why ExtensibilityTask inherits from AppDomainIsolatedTask instead of Task.

GenEngine.StartInfo.GenerationDirectory is the directory where new GenItems are generated.

engine.StartInfo.GenerationDirectory = OutputPath;

As you see in the Target file, this maps to IntermediateOutputDirectory. That is, by default, the "obj/Debug|Release" folder.

RunHistory is a file which keeps track of generated files after every GenEngine.Run, it keeps track of generated files and timestamp. Its goal is to see if files were modified between two runs so that GenEngine can set the value of GenItem.Modified before every run.

The other goal is to include the generated files of the last run to the list of GenEngine.SourceFiles before each run.

Conclusion

I worked hard to make the simplest design possible to implement Genuilder. And I'm happy to see there is not much lines of code.

I hope that Genuilder will make your projects a little more simple, and that its design is simple enough so that you don't need to know MSBuild to understand how Genuilder works.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)

Share

About the Author

Nicolas Dorier
Software Developer Freelance
France France
I am a trainer and a curious developer.
 
CEO of AO-IS, we created a tool to make IaaS on Azure more easy IaaS Management Studio.
 
If you are interested for working with me, for fun coding stuff, for freelance stuff, or interested in using our cloud training infrastructure freely for a kickass presentation for the dev community ? this way Smile | :)

Comments and Discussions

 
GeneralMy vote of 5 PinmvpKanasz Robert5-Nov-12 3:42 
GeneralRe: My vote of 5 PinmemberNicolas Dorier5-Nov-12 7:09 
Questionwhat are the requirements for installation? PinmemberSouthmountain26-Nov-11 12:24 
AnswerRe: what are the requirements for installation? PinmemberNicolas Dorier26-Nov-11 13:00 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.1411023.1 | Last Updated 30 Nov 2011
Article Copyright 2011 by Nicolas Dorier
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid