Thursday, October 25, 2012

Some tricks for MsBuild + VStudio

Many developers that I work with avoid working with msbuild. This is a shame, since a little msbuild knowledge can go a long way. Here are some tips to help others leverage msbuild along with VStudio

Use .targets and .proj

Both extensions are common for MSBuild files. So which to use? I use the following to help clarify a MSBuild files purpose:

  • *.targets used for projects that are to be imported, these are generally MSBuild files that have little use unless imported into another MSBuild project.
  • *.proj used for projects that have their own useful targets. These are generally MSBuild files that contain targets to be called from command line.

Edit you .csproj to include a .targets project

A great way to leverage msbuild with your normal visual studio (.csproj) project, is to edit the .csproj and import a corresponding .targets file. As a convention, I name the import after the .csproj file, as an example; for a project MyApp.csproj I use a MyApp.targets import. Simply add the following in your .csproj file:

<Import Project="$(MSBuildThisFileName).targets" Condition="Exists('$(MSBuildThisFileName).targets')" />

After importing, you can now make interesting extensions to the build process using the BeforeTargets and AfterTargets attributes

As of VStudio 2012, I can edit my custom MyApp.targets and, without re-loading the MyApp.csproj the changes take effect.

A target naming approach

As your MSBuild project get more complex, you may find (like me) that it can help to use a kind of 2-Target approach. In such cases I try and use a Short name for outer; descriptive name for inner style.

My reasoning is that I use an 'outer' Task from the command line, wehere I want a short easier-to-type name. Whilst, when building the task(s) I want more descriptive names, for future-maintainability. For example, I might have a task makeHelp that calls an internall task AddItemsFromAdditionalPaths.

Use the MSBuild task

To help modularize, use the MSBuild task to 'call' an external project. In the child task, define an Output attribute, and assign a resulting item list to it. Now in the caller (parent) task simply assign the items using TargetOutputs.

Extend when need

MSBuid is surprisingly easy to extend. There are 2 well known extension libraries: MSBuild Extension Pack and MSBuild Community Tasks. If neither suite, its really easy to just create your own.

As another option, you can also inline a task (MSBuild in-line task). But I would recommend NOT doing this. A task is easier to understand when written as a C# class, rahter than in-line.

Leverage your .csproj project

Just as I like to extend my .csproj using a .targets file. It can also be useful to go the other way and build a .proj that imports your .csproj

The Visual Studio IDE is great for maintaining source files, and content files and other files in your project. You can use the UI to visually add and organize project files.

On occasion I take advantage of this using a MyApp.proj MSBuild project that imports MyApp.csproj. This way I can build targets that have access to ItemGroup's such as @(Compile) and @(Content)

Property Functions come in handy

Property Functions can be used to manipulate a property, the string manipulation is one of the more useful capabilities. Unfortunately the syntax is verbose, and can be challenging to read, so I tend to limit to simpler uses.

One of the more useful tricks, is to use property functions with item meta-data. To do this 'convert' meta-data to a string, and use it e.g

    $([System.String]::new('%(RelativeDir)')).Replace('Stub', '$(OutPath)')

Item Metadata for 'special' task processing

Metadata can be a clean way to extend an existing ItemGroup, allowing Tasks to alter how they behave. I prefer to use metadata over creating 'working' ItemGroups. If I find myself creating an ItemGroup just for a Task, e.g

<ItemGroup>
  <LogFilesToArchive />
<ItemGroup>

I prefer

<ItemGroup>
  <LogFiles Include="">
    <archive>$(oldFiles)</archive>
  </LogFiles>
</ItemGroup>

Using meta-data does have a down-side; you need to ensure all Items have the meta-data otherwise msbuild complains. To solve this use a ItemDefinistionGroup, or update the items with:

<ItemGroup>
  <LogFiles Include="@(LogFiles)">
    <archive></archive>
  <LogFiles>
</ItemGroup>

Using DependsOn in .csproj

VStudio uses DependsOn metadata to nest one item under another (like web.config and web.debug.config). You can do this for your own files. For example if you use partials, edit the .csproj to group all the files under one.

Add to ItemGroup with Recursive.

VStudio has a number of well-known item groups, such as @(Compile) and @(Content).

If you edit the .csproj, you can manually add items to these ItemGroups. It can be a faster / easier way to add items, rather then using the UI. I've even use recursive includes to add tree's of files to particular itemgroup's.

No comments:

Post a Comment

A short list of C# coding issues.

Injecting services into entities It been bugging me for a while now that to use services in a entity can be a bit of pain. It usually starts...