Skip to main content
Email Password   helpLost your password?
Download Genuilder-1.1-bin.zip - 1.03 MB
Download Genuilder-1.1-src.zip - 898.72 KB
Genuilder's home in Codeplex

Table of contents

Introduction:

First of all, I am very excited to talk about this project, this is maybe the most useful I have ever done (It's not hard stuff, because I love doing useless things too).

Genuilder (the name is a mix between "Generator" and "Builder") is the beginning of a new way to easily extend Visual Studio and your build process in 8 mouse clicks (yeah it's a power of two, a plain round integer)

This idea comes from previous works I've done on MSBuild : the AppConfigVerifier done with Daniel, the AppSettingsGenerator, and a little tool I've done at work, EnvironmentFile, that I'll present during this article.

I thought that these articles haven't the popularity they deserve, I was very happy with them but not so many people have shared their enthusiasm in vote. My guess is that people don't care about MSBuild, so this article is split in two parts.

  1. What Genuilder do, how to you use it
  2. Internals

You can read the first part in 2 or 3 minutes. In 10 min from now, your project will be ready to use Genuilder.

I've done everything to make this project to "just work" in 8 clicks without MSBuild knowledge.

This project will evolve depending on the enthusiasm and ideas you have, I believe there is much potential for futur improvements.

Genuilder is on codeplex, I recommend you to download sources and binaries there!


Installation

Imagine you want to install Genuilder on MyCorpProject :



Download the last release.

Copy the Genuilder directory next to your solution :



In the Genuilder directory execute Genuilder.GUI.exe, open the solution and select your project, then click on the button :



Now MyCorpProject has all the features of Genuilder.
Yes, it's that simple...

Note : The project must not be in read-only

Features

AppConfigVerifier

The AppConfigVerifier is an integration in Genuilder of this project.
It scans all 'type=".*"' in the config file, and check 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:



Genuilder will find the error at compile time:



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 to your AppSettings and ConnectionStrings in a strongly typed way.
Example, the appconfig :

Auto completion on AppSettings :

Auto completion on ConnectionStrings :


EnvironmentFile

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


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



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



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

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


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



Note : this feature doesn't work for web.config files. (Don't worry .NET 4.0 is coming with a new feature for web.config files)

InterfaceExtractor

Interface extractor is a code generator which extract 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.
I'll create an article on codeproject on a nice use case with ASP.NET MVC to control authorized access of a controller. (I have not done and I've not eat my own dog food yet... so be really careful with this feature ! ;))

For example this class :



Will create extract this interface :



Note that the parameter of the 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

As InterfaceExtractor, PropertiesExtractor is a class generator which generate a static class to access properties name in a strongly typed way.
For example this class implement INotifyPropertyChanged and do not hard code the property name with a string :



Because PropertiesExtractor will generate this other class



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 generate a class to access to your configuration file in a strongly typed way.
If you remove a section whereas your code somewhere has a dependency on it, it will break the code at compile time instead of during the runtime.

For example for this config file :



You will be able to access to sections like this :



Known problems with Visual Studio

These bugs are fixed in version 1.1.

My code doesn't compile the first time

There is a bug in Visual Studio 2008, when you compile generated classes are generated before the compilation;
but for some obscure reasons, Visual studio will fail the compilation if you already use these generated classes in your code, although Intellisense works well and that the classes are generated.
But the next compilation will not fail.

Example, you code this class in one step before compiling the first time :



Visual studio says :



The next time you modify the file where Person is and rebuild, you won't see the error.

This is clearly a bug, which doesn't appear when you build with MSBuild in command line...
It seems that visual studio compile the class generated before you decide to build.

My code should break at compile time but it doesn't

This bug is caused by the same root as this bug.

You have this class:



You refactor the property's name to 'Year'.
The constructor should break since it references PersonProperties.Age instead of PersonProperties.Year.

But Visual studio doesn't complain :



The next time you modify the file where Person is and rebuild, it will break as expected.

How I've fixed these bugs

These bugs was caused by visual studio which uses an inprocess compiler instead of Csc.exe, source :MSBuild Team. The trick was just to add this line in the AllTargets.targets to force Visual Studio to use Csc.exe. 

<PropertyGroup>
	<UseHostCompilerIfAvailable>false</UseHostCompilerIfAvailable>
</PropertyGroup>

Internals

How do you do that ? It's awesome !

I hope you are as enthusiast as me on this project, in fact it is stupid simple. When you run Genuilder GUI on your project, there is only two lines in your project file.

I import AllTargets.targets,

<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\bin\AllTargets.targets" /> <!-- This line ! -->

And I add a reference on Genuilder.dll (for now it contains only two class, ExtractInterface and ExtractProperties )

<Reference Include="Genuilder">
	<HintPath>..\bin\Genuilder.dll</HintPath>
</Reference>

That's it ! And here is the AllTargets.targets file. If you want to disable a feature just comment a line.

<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
	<Import Project="AppConfigVerifier.targets" />
	<Import Project="AppSettingsGenerator.targets" />
	<Import Project="EnvironmentFile.targets" />
	<Import Project="InterfaceExtractor.targets" />
	<Import Project="PropertiesExtractor.targets" />
	<Import Project="StrongConfigGenerator.targets" />
</Project>

Most of these targets files are not complicated, they insert their targets in the build process and invoke a task. For example here is AppConfigVerifier.targets (as documented in my previous article AppSettingsGenerator). Note that StrongConfigGenerator and AppSettingsGenerator are very similar, you can also change the name and the namespace of generated classes in these files.

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
	<UsingTask TaskName="AppConfigVerifier.MSBuild.Verifier" AssemblyFile="AppConfigVerfier.MSBuild.dll" />
	<PropertyGroup>
		<BuildDependsOn>
			$(BuildDependsOn);
			AppConfigVerifier_GetConfigFile;
			VerifyAppConfig
		</BuildDependsOn>
	</PropertyGroup>

	<Target Name="AppConfigVerifier_GetConfigFile">
		<ItemGroup>
			<_ConfigFile Include="@(AppConfigWithTargetPath)" Condition="Exists('@(AppConfigWithTargetPath)')" />
			<_ConfigFile Include="Web.config" Condition="Exists('Web.config') And '@(_ConfigFile)' == ''" />
		</ItemGroup>
	</Target>

	
	<Target Name="VerifyAppConfig" Inputs="@(_ConfigFile)" Outputs="$(IntermediateOutputPath)Verifier_LastCheck.txt" Condition="Exists('@(_ConfigFile)')">
		<ItemGroup>
			<DefaultReferencePath Include=" @(MainAssembly->'%(FullPath)')"></DefaultReferencePath>
		</ItemGroup>
		<AppConfigVerifier.MSBuild.Verifier References="@(ReferencePath)" DefaultReferences="@(DefaultReferencePath)" Config="@(_ConfigFile)">
		</AppConfigVerifier.MSBuild.Verifier>

		<WriteLinesToFile File="$(IntermediateOutputPath)Verifier_LastCheck.txt" Lines="LastModified" Overwrite="true">
		</WriteLinesToFile>
		
		<ItemGroup>
			<FileWrites Include="$(IntermediateOutputPath)Verifier_LastCheck.txt"/>
		</ItemGroup>
	</Target>
</Project>

I use the log of MSBuild to send messages to the Error List of Visual Studio.

In a near future, I will add MSBuild properties to easily disable some targets for some projects.

So that's it, as you see it's stupid simple why nobody has thought about this earlier ?

Interesting stuff I have done with MSBuild during the development of this project.

My favorite principle in development is DRY, Don't Repeat Yourself. I'll show you how MSBuild is all about that.

This solution has three projects:

I wanted to automate three things :

  1. Every targets files should be added to AllTargets.targets. (Do you remember ? it's the file which is imported in your project file with my GUI)
  2. Every project with a Genuilder feature (ie, a targets file) must generate their output in a common directory.
  3. Every project sample must import the targets file it is supposed to test.

I adopted a convention with the name of my projects.

All projects which contains a new feature to include in the AllTargets.targets are called *.Targets

All project samples end with XXXXX.TestProject(x?), where XXXXXX is the name of the tested project "XXXXX.Targets"

For example if I create a new Targets project called NewFeature.Targets, and a new project sample called NewFeature.TestProject, they will be configured in one keystroke.

With MSBuild I will decrease manual work to do when I create a new project, and improve readability by following conventions.

The project "SetCommonConfigOnTargets" in Genuilder.sln, is a command line utility which :

  1. Import a targets file called "Common.Before.targets" in all .Targets project.
  2. Import the targets file XXXX.targets in every XXXX.TestProject.csproj.

Here is "Common.Before.targets", it sets the output directory of all .Targets project to a common directory.

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
	<PropertyGroup>
		<OutDir>..\bin</OutDir>
	</PropertyGroup>
</Project>

During the compilation of the project CommonConfig, SetCommonConfigOnTargets.exe is called, projects are configured, and AllTargets.targets is created.

CommonConfig references automatically all .Targets projects, this way they are compiled before CommonConfig. Yeah, it's automatic, and it's one line in the project file:

<ProjectReference Include="..\*.Targets*\*.csproj" />

I also store every targets files in the a MSBuild item called "TargetsFiles".

<TargetsFiles Include="..\**\*.targets" Exclude="..\**\bin\**\*;..\$(AssemblyName)\*.targets;..\*TestProject*\**\*.targets" />

Then I redefine what it means to "Compile" the project, ie, Create the AllTargets.targets file, and execute SetCommonConfigOnTargets.exe, to ensure that all projects which follow the convention are configured.

<Target Name="Compile">
	<Message Text="@(TargetsFiles)">
	</Message>
	<ItemGroup>
		<_LinesToWrite Include="<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">" />
		<_LinesToWrite Include="@(TargetsFiles -> '<Import Project="%(FileName)%(Extension)" />')" />
		<_LinesToWrite Include="</Project>" />
	</ItemGroup>
	<WriteLinesToFile File="AllTargets.targets" Lines="@(_LinesToWrite)" Overwrite="true">
	</WriteLinesToFile>
	<Message Text="$(Language)">
	</Message>
	<Copy SourceFiles="AllTargets.targets" DestinationFolder="$(OutDir)">
	</Copy>
	<Exec Command="SetCommonConfigOnTargets.exe" WorkingDirectory="..\SetCommonConfigOnTargets\bin\$(Configuration)">
	</Exec>
</Target>

I also had to override a build step called CopyFilesToOutputDirectory to make it works, because CommonConfig doesn't generate an assembly.

<Target Name="CopyFilesToOutputDirectory" />

This way when I compile CommonConfig.csproj, everything is well configured without manual intervention.

Conclusion:

Well that's it, I wait your feedback and some ideas on new features I should add to Genuilder. If you like it and if you use it, please spread the word. :D  

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralExcellent stuff Pin
Pete O'Hanlon
3:14 10 Nov '09  
GeneralRe: Excellent stuff Pin
Nicolas Dorier
3:42 10 Nov '09  
GeneralGreat man Pin
Sacha Barber
7:21 9 Nov '09  
GeneralRe: Great man Pin
Nicolas Dorier
10:37 9 Nov '09  
Generala few words for "mere mortals" ? [modified] Pin
BillWoodruff
1:53 9 Nov '09  
GeneralRe: a few words for "mere mortals" ? Pin
Nicolas Dorier
2:44 9 Nov '09  
GeneralRe: a few words for "mere mortals" ? Pin
BillWoodruff
23:48 9 Nov '09  
GeneralRe: a few words for "mere mortals" ? Pin
Nicolas Dorier
10:35 9 Nov '09  
GeneralExcellent Work.... Really Good!!! Pin
WhiteKnight
18:44 7 Nov '09  
GeneralRe: Excellent Work.... Really Good!!! Pin
Nicolas Dorier
23:22 7 Nov '09  
GeneralCould you explain Interface and Property extractors? [modified] Pin
Paulo Zemek
17:21 7 Nov '09  
GeneralRe: Could you explain Interface and Property extractors? Pin
Nicolas Dorier
23:13 7 Nov '09  
GeneralRe: Could you explain Interface and Property extractors? Pin
Paulo Zemek
2:56 8 Nov '09  
GeneralThe reason of the bugs... Pin
Paulo Zemek
17:03 7 Nov '09  
GeneralRe: The reason of the bugs... Pin
Nicolas Dorier
22:09 7 Nov '09  
GeneralRe: The reason of the bugs... Pin
Nicolas Dorier
22:40 7 Nov '09  
GeneralRe: The reason of the bugs... Pin
Nicolas Dorier
23:36 7 Nov '09  
GeneralRe: The reason of the bugs... Pin
Nicolas Dorier
23:40 7 Nov '09  
GeneralGreat work Pin
Daniel Vaughan
9:33 7 Nov '09  
GeneralRe: Great work [modified] Pin
Nicolas Dorier
9:46 7 Nov '09  
GeneralRe: Great work Pin
Daniel Vaughan
12:57 7 Nov '09  


Last Updated 10 Nov 2009 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2009