Anda di halaman 1dari 17

Quick Intro to MSBuild Projects

Published Sep 15, 2015Last updated Apr 12, 2017

Have you ever opened up a .csproj file (the project file created by Visual
Studio for C#) and wondered what all that gobbligook was all about?
This is a quick overview introduction to MSBuild projects so that you can
read and understand Visual Studio project XML syntax right away.

Did you know that Visual Studio Projects are MSBuild


Scripts?!
I always knew this at a cursory level, since after all I knew that MSBuild was
building my projects and I knew that the projects had what MSBuild needed
to get the job done. But I never knew that these were not just manifests and
high level metadata but you can actually have full control over the way
MSBuild does things--even invoke code, even C# code--using semantics that
are surprisingly expressive and flexible.

It's all in the XML


To get started, create a new C# project in Visual Studio, any "normal" app
such as a console application, class library, Windows Forms app, or WPF
app. You might even create a Web App.

Once created, open up the .csproj file and take look. Here's a default
console app.

<?xml version="1.0" encoding="utf-8"?>


<Project ToolsVersion="14.0" DefaultTargets="Build"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<Import
Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props"
Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Commo
n.props')" />

<PropertyGroup>

<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>

<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>

<ProjectGuid>{64AD1366-8340-4451-973F-36CC0CC76280}</ProjectGuid>

<OutputType>Exe</OutputType>

<AppDesignerFolder>Properties</AppDesignerFolder>

<RootNamespace>ConsoleApplication5</RootNamespace>

<AssemblyName>ConsoleApplication5</AssemblyName>

<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>

<FileAlignment>512</FileAlignment>

<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>

</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

<PlatformTarget>AnyCPU</PlatformTarget>

<DebugSymbols>true</DebugSymbols>

<DebugType>full</DebugType>

<Optimize>false</Optimize>

<OutputPath>bin\Debug\</OutputPath>

<DefineConstants>DEBUG;TRACE</DefineConstants>

<ErrorReport>prompt</ErrorReport>

<WarningLevel>4</WarningLevel>

</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU'


">

<PlatformTarget>AnyCPU</PlatformTarget>

<DebugType>pdbonly</DebugType>

<Optimize>true</Optimize>

<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>

<ErrorReport>prompt</ErrorReport>

<WarningLevel>4</WarningLevel>

</PropertyGroup>

<ItemGroup>

<Reference Include="System" />

<Reference Include="System.Core" />

<Reference Include="System.Xml.Linq" />

<Reference Include="System.Data.DataSetExtensions" />

<Reference Include="Microsoft.CSharp" />

<Reference Include="System.Data" />

<Reference Include="System.Net.Http" />

<Reference Include="System.Xml" />

</ItemGroup>

<ItemGroup>

<Compile Include="Program.cs" />


<Compile Include="Properties\AssemblyInfo.cs" />

</ItemGroup>

<ItemGroup>

<None Include="App.config" />

</ItemGroup>

<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

<!-- To modify your build process, add your task inside one of the targets below
and uncomment it.

Other similar extension points exist, see Microsoft.Common.targets.

<Target Name="BeforeBuild">

</Target>

<Target Name="AfterBuild">

</Target>

-->

</Project>

Let's break this down.


The Most Important Components
PropertyGroup Blocks and Properties
<PropertyGroup>

<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>

<!-- ... -->

</PropertyGroup>

<PropertyGroup> blocks allow you to define properties and their values. If a


property already existed its value will be updated, otherwise the property will
also be created. These properties are named by tag name and their value
declared by text value. So in the above example, the property name is
"Configuration", and the value is "Debug". There is also a condition on this
property, which is "'$(Configuration)' == ''". This basically means that this
value assignment is only applied if there isn't already a value, and there will
be no value (that is, the value will be empty string) if
the Configuration property has never been declared prior to this, in which
case this PropertyGroup is declaring this property for the first time.

You read these properties out anywhere in the MSBuild script (such as the
Condition here but elsewhere too) using the syntax " $(PropertyName)". You
can even read the same property while updating itself to append it .. for
example ..

<PropertyGroup>
<PropertyA>A</PropertyA>

<PropertyB>B</PropertyB>

<PropertyAB>$(PropertyA).$(PropertyB)</PropertyAB>

<PropertyA>$(PropertyA)X</PropertyA>

</PropertyGroup>

In this example, PropertyAB's value becomes "A.B". A property can also be


replaced. These declarations are sequentially evaluated. So while
PropertyAB is "A.B", PropertyA is "AX".

Sometimes properties are imported from a ".props" file, i.e.


"MyProperties.props". This is done using the syntax:

<Import Project="MyProperties.props" />

The syntax of the contents of the .props file is exactly the same as the
project, given the necessary <Project> container element.

<?xml version="1.0" encoding="utf-8"?>

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<PropertyGroup>

<MyCustomProperty>my value</MyCustomProperty>
</PropertyGroup>

</Project>

Property Functions
As mentioned, properties can be read into attributes or values using the
syntax $(PropertyName). You can also evaluate .NET 4+ functions in properties
using the syntax $([CLASS]::METHOD(arguments)) or $([CLASS]::PROPERTY). For
example,

<PropertyGroup>

<BuildTime>$([System.DateTime]::Now</BuildTime>

</PropertyGroup>

Items
As we know, Visual Studio projects normally consist of file manifests. The
assumption made by some, prior to learning this detail about MSBuild
scripts, is that a project is just a manifest plus some properties that get
passed on to a compiler, or something, whatever. Well, the manifest part is
certainly true. The format of this manifest is, of course, the combination of
the <ItemGroup> blocks.

<ItemGroup>

<Compile Include="Program.cs" />


<Compile Include="Properties\AssemblyInfo.cs" />

</ItemGroup>

<ItemGroup>

<None Include="App.config" />

</ItemGroup>

As with <PropertyGroup> tags, ItemGroup tags are simply combined and their
being broken up into multiple groups is largely inconsequential. Having
multiple groups split as such does offer some flexibility to add conditions or
metadata on specific groups, however.

The tag names used for items under <ItemGroup> are the item types. These
also correlate to the Build Action property that you see in Visual Studio in the
Solution Explorer for a particular file. The "None" item type calls for the
"None" Build Action. The "Compile" item type calls for the file to be compiled,
using the compiler associated with the project (i.e. csc, which is the C#
compiler). Generally these are added to a list of parameters for one csc
invocation, not typically isolated to separate invocations. "Content" marks it
as content. And so the list goes on.

<Reference> items obviously aren't file manifest items, but are still part of the
manifest.
All item types have an Include=".." attribute. Some item types call for
additional attributes or even nested tags, but these go beyond the scope of
this article. As with properties, item types can be custom set for your own
use.

We will circle around back to items when we look at consuming them in


tasks, but here's a hint: Items of a common type can be iterated over using
the '%' character.

Targets
You can think of targets as named sets of instructions of tasks. They are like
named methods or functions or subroutines.

<Target Name="MyCustomTarget" AfterTargets="Build">

<Message Text="Hello World" Importance="high" />

</Target>

In this example, a new target named "MyCustomTarget" is declared. It will be


invoked after Build has been invoked. The target will invoke the Message task,
causing the Output window in Visual Studio to output "Hello World".
(The Importance="high" attribute enables it to appear with the default log
verbosity in Visual studio.)

This is functionally equivalent to a C# method that might look like this


pseudocode:
[InvokeAfter("Build")]

public static void MyCustomTarget() {

Log.WriteLine("Hello World", LogImportance.high);

I call this "pseudocode" because the above function has no real world
invocation scenario, but it sure reads understandably.

Targets are sometimes imported into a project from ".targets" files.

<Import Project="MyTargets.targets" />

As with the .props file mentioned earlier, the syntax of the contents of the
imported file is just the same as the project XML syntax including
the <Project> container element.

<?xml version="1.0" encoding="utf-8"?>

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<Target Name="MyCustomTarget" AfterTargets="Build">

<Message Text="Hello World" Importance="high" />

</Target>

</Project>
Nested Properties and Items
A custom <Target> can declare its own <PropertyGroup> and its
own <ItemGroup> (and, of course, declare things within them). The scope of
properties or property overrides when declared within the <Target> is applied
by that <Target> when it executes.

For example:

<PropertyGroup>

<MyProperty>This is the default value.</MyProperty>

</PropertyGroup>

<Target Name="MyTarget">

<Message Text="$(MyProperty)" Importance="high" />

</Target>

<Target Name="AnotherTarget">

<PropertyGroup>

<MyProperty>This is the overridden value.</MyProperty>

</PropertyGroup>

<Message Text="$(MyProperty)" Importance="high" />


</Target>

<Target Name="ExecuteCustomTargets" AfterTargets="Build">

<CallTarget Targets="MyTarget" />

<CallTarget Targets="AnotherTarget" />

</Target>

If the above example was pasted directly into your project, the resulting
output in the Output log window would look like this:

This is the default value.

This is the overridden value.

Tasks
In the Targets section above where an example Target was declared, the
example included a task. That task was <Message>. MSBuild ships out-of-the-
box with a large number of built-in tasks that you can execute in your targets.
You can find them documented here.

Community Tasks
You can also make use of a number of additional tasks created by the
MSBuild community. Particularly of interest is the project known as "MSBuild
Community Tasks" which is available here.
Getting started with MSBuild Community Tasks
To get started with MSBuild Community Tasks, these are the steps I found
worked for me.

1. In Visual Studio, open the Package Manager console window and


execute: Install-Package MSBuildTasks

2. The solution now has a new .build virtual folder. Open the .targets file.

3. In the <PropertyGroup> block at the top, remove the first property, then
modify the second property so that only the filename of the DLL is in
the string (no directory, no parenthesis).

4. Just before your custom <Target> in your project, add:


<Import

Project="$(MSBuildProjectDirectory)\..\.build\MSBuild.Community.Tasks.tar

gets" />

Note that this assumes that the project directory is contained in the
solution directory, otherwise you can eliminate the "..".

5. Now you can add one or many of the documented community tasks.
Try <Beep /> to validate things are working.

UsingTask Declarations
The <UsingTask> declaration enables you to use tasks in your target(s) that
you would otherwise not have access to. You can use the <UsingTask> XML
element to declare one of at least a couple different things:

1. Import a task or tasks from an external DLL, such as TransformXml.


Or,
2. Declare a new custom task, such as an inline task. An inline task is a
task that is created inline in the project and can be written in C#.

An example of importing tasks:

<UsingTask TaskName="TransformXml"
AssemblyFile="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0\Web\Microsof
t.Web.Publishing.Tasks.dll" />

An example of an inline task and invoking it:

<UsingTask TaskName="Hello" TaskFactory="CodeTaskFactory"


AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >

<ParameterGroup />

<Task>

<Code Type="Fragment" Language="cs">

Log.LogMessage("Hello, world!", MessageImportance.High);

</Code>

</Task>

</UsingTask>

<Target Name="TestBuild">

<Hello />
</Target>

Iterating over Items


I mentioned earlier that you can iterate over items using the % character. This
sounds gnarly, but it's actually quite handy. Consider this example:

<ItemGroup>

<Attendee Include="Fred">

<Guest>Ruth</Guest>

</Attendee>

<Attendee Include="John">

<Guest>Michael</Guest>

</Attendee>

</ItemGroup>

<Target>

<Rsvp Attendee="%(Attendee.Identity)" Guest="%(Attendee.Guest)" />

</Target>

In this example, a custom task called Rsvp is being invoked twice. MSBuild
will iterate over all of the different items in the list referenced
by %(Attendee.Identity)and pass each one in as a parameter in a separate
invocation of the task. The .Identity reference refers to the value of
the Include="" attribute of the item.

Default Targets
When Visual Studio builds, it iterates over the semicolon-delimited list of
values in the DefaultTargets attribute of the <Project> node of the project file.
So if you want your custom target to always execute whenever Visual Studio
builds the project (or whenever the MSBuild shell command is executed
pointing at your project without specifying the target name) then you might
consider just putting your target in the DefaultTargets attribute by appending
a semicolon and your target's name.

What Will You Do Now?


If you got this far and understand it all, you should now be able to open up a
.csproj file or .vbproj file and fully understand exactly what is going on, and
you might even consider getting a little creative with automating your build
scripts with MSBuild. Good luck.

Anda mungkin juga menyukai