- | rssFeed | My book on MSBuild and Team Build | Archives and Categories Friday, October 22, 2010

MSBuild: Extending the solution build

One of the most commonly asked questions that I get is “How can I extend the build process for my solution?” In fact I was asked this soo much that 4 years ago (wow I’ve been dealing with MSBuild for far too long:) ) I wrote a blog post on Use MSBuild to build Solution files (no longer recommended technique) and that post to this day is one of the most frequently visited ones. Well in the 4+ years that have passed I’m glad to say that things have changed. The problem is that almost no one knows about it. Hopefully this blog post will be the start to that changing.

What’s under the covers

In all versions of Visual Studio, the solution file is not an MSBuild file. Which sucks for many of us. Essentially the solution build was like a black box.

Blackbox

Even in VS2010 it still kinda is, but there is a hook for us to extend it built in. Let me explain what happens when you build a solution using msbuild.exe. When you build a solution using msbuild.exe the solution file is converted to an MSBuild file in memory then that MSBuild file is used to build the solution itself. When you build from the command line here is the rough outline of the MSBuild project that gets built.



  
  
  

  
  
  
  
  
                    
  

OK here I have left out the targets and properties because they are irrelevant to this discussion. Take a look at these import statements. There are two pairs of import statements ones that are imported before the build is defined and the imports which are after the build definition. Instead of looking at the first import statement lets look at the second one, because I think its more compelling and its easier to demo.

This statement is pretty simple, if a file with the name of before.ExtendSolutionBuild.sln.targets exists in the same directory as the ExtendSolutionBuild.sln file then it will be imported in at the top of the build file. Also there is a corresponding entry for after.ExtendSolutionBuild.sln.targets. In stead of getting into all the nitty gritty of why two exists let me just boil it down. You should place properties/items inside of the before targets file, and inside of the after .targets file you should place targets but its OK to put properties/items here as well. The most important thing to keep a note of is that you cannot place anything inside of the before file which relies on any properties/items defined in the build because they will not be available. Because of this most times you will just need an after targets file. So in my case I will create a file named after.ExtendSolutionBuild.sln.targets, this follows the pattern after.{SolutionFile}.sln.targets where {SolutionFile} is the name of the solution file mine happens to be ExtendSolutionBuild. Every solution has 4 targets that will be defined.

4 Targets on Every Solution file

So let’s say that I want to extend the build file and inject two targets; GenerateCode and RunCodeAnalysis. Obviously I want the GenerateCode target to run before anything is built and the RunCodeAnalysis to be run after everything is built. So in my after.ExtendSolutionBuild.sln.targets file I place the following content.



  
  
    
  

  
    
  
  

You can see here that I am using BeforeTargets=”Build” to make sure that the GenerateCode target is run before the Bulid target and also the RunCodeAnalysis target uses AfterTargets=”Build”. So now let’s see what happens when I build my solution from the command line using msbuild.exe.

ExtendSolution01

From the figure above you can see that the GenerateCode target was executed before the build process started and then the RunCodeAnalysis target was executed after the build. With MSBuild 3.5 and earlier this was simply not possible, and you would have had to write another build file to do these kinds of things for you. If you are building solution files from the command then you might be interested in the technique. I would be interested to hear you feedback and to see how you guys would use this feature.

ImportBefore/ImportAfter

Now let’s talk briefly about the two that we skipped. If you want an MSBuild file to be imported for every solution that is build using msbuild.exe for a given machine then you can place that file in one of the two folders below.

So if you place any file in either of those folders (which don’t exist by default) then they will automatically be imported for every solution file which builds on that machine using msbuild.exe. Typically you do not run into many times where you want to do this, but the times that you do then it is critical and really hard to work around without something like this. This is useful if you have a build machine and you want to perform a set of actions for every build.

Resources

Sayed Ibrahim Hashimi | @SayedIHashimi

msbuild | MSBuild 4.0 | Visual Studio 2010 Friday, October 22, 2010 7:14:22 AM (GMT Daylight Time, UTC+01:00)  #     | 
Thursday, October 21, 2010

ASP.NET Web Projects: web.debug.config & web.release.config

Warning: What you see below feels hacky to me, but if you find it useful then use it

I have heard a lot of questions and confusion regarding web.debug.config and web.release.config. For example here is just one question on StackOverflow. The question states:

Hello, I want to use the web.config transformation that works fine for publish also for debugging.

When i publish a web app, visual studio automatically transforms the web.config based on my 
current build configuration. How can i tell visual studio 
to do the same when i start debugging. On debug start it simply 
uses the default web.config without transformation.

Any idea?

First let me explain, as I did to that question, the purpose of the files: web.config/web.debug.config/web.release.config.

web.config

This is the config file which developers should use locally. Ideally you should get this to be standardized. For instance you could use localhost for DB strings, and what not. You should strive for this to work on dev machines without changes.

web.debug.config

This is the transform that is applied when you publish your application to the development staging environment. This would make changes to the web.config which are required for the target environment.

web.release.config

This is the transform that is applied when you publish your application to the "production" environment. Obviously you'll have to be careful with passwords depending on your application/team.

The problem with transforming the web.config that you are currently running is that a transform can perform destructive actions to the web.config. For example it may delete a attributes, delete elements, etc.

Resolution

Ok, with that out the way not let’s see how we can enable what the question asker wants to do. To recap, when he builds on a particular configuration he wants a specific transform to be applied to web.config. So obviously you do not want to maintain a web.config file, because it is going to be overwritten. So what we need to do is to create a new file web.template.config, which is just a copy of web.config. Then just delete web.config by using Windows Explorer (don’t delete using Visual Studio because we do not want to delete it from the project). Note: If you are using a source control provider which is integrated into Visual Studio then you probably want to delete web.config from source control. Also with this we do not want to use web.debug.config or web.release.config because these already have a well defined role in the Web Publishing Pipeline so we do not want to disturb that. So instead we will create two new files, in the same folder as the project and web.template.config, web.dev.debug.config and web.dev.release.config. The ideas is that these will be the transforms applied when you debug, or run, your application from Visual Studio. Now we need to hook into the build/package/publish process to get this all wired up. With Web Application Projects (WAP) there is an extensibility point that you can create a project file in the same folder with the name {ProjectName}.wpp.targets where {ProjectName} is the name of the project. If this file is on disk in the same folder as the WAP then it will automatically be imported into the project file. So I have created this file. And I have placed the following content:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <!-- Make sure web.config will be there even for package/publish -->
  <Target Name="CopyWebTemplateConfig" BeforeTargets="Build">
    <Copy SourceFiles="web.template.config"
          DestinationFiles="web.config"/>
  </Target>
  
  <PropertyGroup>
    <PrepareForRunDependsOn>
      $(PrepareForRunDependsOn);
      UpdateWebConfigBeforeRun;
    </PrepareForRunDependsOn>
  </PropertyGroup>

  <!-- This target will run right before you run your app in Visual Studio -->
  <Target Name="UpdateWebConfigBeforeRun">
    <Message Text="Configuration: $(Configuration): web.dev.$(Configuration).config"/>
    <TransformXml Source="web.template.config"
              Transform="web.dev.$(Configuration).config"
              Destination="web.config" />
  </Target>

  <!-- Exclude the config template files from the created package -->
  <Target Name="ExcludeCustomConfigTransformFiles" BeforeTargets="ExcludeFilesFromPackage">
    <ItemGroup>
      <ExcludeFromPackageFiles Include="web.template.config;web.dev.*.config"/>
    </ItemGroup>
    <Message Text="ExcludeFromPackageFiles: @(ExcludeFromPackageFiles)" Importance="high"/>
  </Target>
</Project>

Let me explain this a bit. I have created the CopyWebTemplateConfig target which will always copy web.template.config to web.config on build, even if you are not debugging your application in Visual Studio. This is needed because we still need to support the package/publish process of Visual Studio. Then I extended the property PrepareForRunDependsOn to include the UpdateWebConfigBeforeRun target. This property is used to identify the list of targets which needs to be executed before any managed project is run from Visual Studio. In this target I am using the TransformXml task to transform web.template.config, using the correct web.dev.***.config file. After that your app starts up using the correct web.config based on  your build configuration.

After that I have another target ExcludeCustomConfigTransformsFiles, which I inject into the package/publish process via the attribute BeforeTargets=”ExcludeFilesFromPackage”. This is needed because we do not want these files to be included when the application is packaged or published.

So that is really all there is to it. To explain the package/publish process a bit more for this scenario. When you package/publish web.debug.config or web.release.config, depending on build configuration, will still be used. But ultimately the file that it is transforming is web.template.config, so you may have to adjust depending on what you have in that file. Questions/Comments?

Sayed Ibrahim Hashimi - @sayedihashimi

msbuild | MSDeploy | Web Publishing Pipeline Thursday, October 21, 2010 7:13:26 AM (GMT Daylight Time, UTC+01:00)  #     | 

MSBuild: Filter list take 2

Last night I saw this tweet.

@MikeFourie MSBuild Q: given I have @(ProjectOutputs) how can I get a collection of all *.Unit.dll files?

About a year ago I blogged about inline tasks including a FilterList task. So for his case the FilterList would work, but then I was thinking that he should be able to do this with some of the goodness that was released wtih MSBuild 4.0. Specifically with Property Function and Item Functions. So I thought about it and whipped up a quick example.

Take a look at the file below.



  
    
    
  
  
  
        
    
      
    
    
  

In this file I have created a dummy item named TargetOutputs and populated it with a bunch of fake assembly names. Then inside the Demo target, I print out the items in that list and construct another item list, UnitTestAssemblies. In this declaration I am including %(TargetOutputs.Identity) with the condition '@(TargetOutputs->Contains("unit"))'=='True'. Let me break this down for you a bit. With Item Functions there is a specific set of functions that you can call like DirectoryName/Metadata/Distinct/etc (see below for the reference link for full list), but you can also execute any string instance method. What you need to understand is that it will execute the method for all items in the list then return a new list with the invocation result. Essentially these are a new form of Item Transformations. I didn’t want to handle these in bulk, I need to apply the filter to each individual item value. In order to do that I use the %(TargetOutputs.Identity) which kicks batching in. Since I can see that the values in the include value are unique I know that the Identity metadata is unique. So what that means is When I do something like the message task will be invoked once per item value in TargetOutputs. The same goes for the item declaration. So in the UnitTestAssemblies item group declaration I can use a condition of Condition='@(TargetOutputs->Contains("unit"))'=='True' , because we are know that @(TargetOutputs) will only have 1 value per invocation. With that condition I only added files that have unit in the name. When you execute the Demo target the result is shown below.

image

Pretty cool huh? The best part, I managed to fit my reply in 140 characters in this tweet.

@HowardvRooijen

Anywayz, if you are not following me on twitter you can find me at @sayedihashimi.

Resources

Sayed Ibrahim Hashimi

msbuild | MSBuild 4.0 Thursday, October 21, 2010 7:11:11 AM (GMT Daylight Time, UTC+01:00)  #     |