In your developer life you have probably worked at
least once with mixed solutions, composed by both Visual Basic and
Visual C# projects because this is not an uncommon situation in.NET
development, or simply with solutions made of application projects and
deployment projects. But while you know how the Visual Basic and C#
compilers work, did you ever wonder how Visual Studio builds executables
from mixed solutions? The answer is the MSBuild.exe, the build engine
from Microsoft that this article discusses.
Introducing MSBuild
MSBuild.exe is a command-line
tool that has been included in the.NET Framework since the 2.0 version
and that can perform advanced and complex compilations. The Visual Basic
compiler (the Vbc.exe tool) enables building an executable from
multiple code files, but if you invoke it from the command line
manually, you need to specify every single code file that must be
compiled to produce the assembly; moreover, you are required to specify
references, imports, and so on. The most important thing is that the VB
compiler cannot build assemblies starting from neither project files nor
from solution files. MSBuild goes beyond such limitations, providing
the ability to build solutions (.sln files), including solutions
composed by mixed projects, project files produced by Visual Studio
(such as Visual Basic .vbproj files) and custom project files in one
command line. For example, the following command line can build a solution without the need to invoke the compiler and specify all other requirements:
To run MSBuild without
specifying the .NET Framework path in the command line, start the Visual
Studio command prompt available in the Visual Studio’s shortcuts folder
in the Windows Start menu.
|
Figure 1 shows the build output in the Console window, where you can get details on the build process.
MSBuild is based on Xml files known as projects.
Project files are easy to create, understand, and extend; projects are
actually nothing new to you, because when you create projects with
Visual Basic, these are compliant with the MSBuild Xml schema (http://schemas.microsoft.com/developer/msbuild/2003). This is the reason why you can build your
projects with MSBuild. Basically a project contains all information
required to build the final executables including code files,
configuration properties, target options, and so on. Let’s see how a
project is structured.
Introducing Projects
The best way for
understanding how projects are structured is opening and analyzing an
existing one. Create a new Console application with Visual Basic 2010,
save it, and close it. Then open the project folder in Windows Explorer
and open the ConsoleApplication1.vbproj file with the Windows Notepad. Listing 1 shows the content of the project file.
Listing 1. A Basic Project File
<?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">x86</Platform> <ProductVersion> </ProductVersion> <SchemaVersion> </SchemaVersion> <ProjectGuid>{A30BC355-A5C8-4C56-97F0-C1A4F0A58222}</ProjectGuid> <OutputType>Exe</OutputType> <StartupObject>ConsoleApplication1.Module1</StartupObject> <RootNamespace>ConsoleApplication1</RootNamespace> <AssemblyName>ConsoleApplication1</AssemblyName> <FileAlignment>512</FileAlignment> <MyType>Console</MyType> <TargetFrameworkVersion>v4.0</TargetFrameworkVersion> <TargetFrameworkProfile>Client</TargetFrameworkProfile> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' "> <PlatformTarget>x86</PlatformTarget> <DebugSymbols>true</DebugSymbols> <DebugType>full</DebugType> <DefineDebug>true</DefineDebug> <DefineTrace>true</DefineTrace> <OutputPath>bin\Debug\</OutputPath> <DocumentationFile>ConsoleApplication1.xml</DocumentationFile> <NoWarn>42016,41999,42017,42018,42019,42032,42036,42020,42021,42022</NoWarn> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' "> <PlatformTarget>x86</PlatformTarget> <DebugType>pdbonly</DebugType> <DefineDebug>false</DefineDebug> <DefineTrace>true</DefineTrace> <Optimize>true</Optimize> <OutputPath>bin\Release\</OutputPath> <DocumentationFile>ConsoleApplication1.xml</DocumentationFile> <NoWarn>42016,41999,42017,42018,42019,42032,42036,42020,42021,42022</NoWarn> </PropertyGroup> <PropertyGroup> <OptionExplicit>On</OptionExplicit> </PropertyGroup> <PropertyGroup> <OptionCompare>Binary</OptionCompare> </PropertyGroup> <PropertyGroup> <OptionStrict>Off</OptionStrict> </PropertyGroup> <PropertyGroup> <OptionInfer>On</OptionInfer> </PropertyGroup> <ItemGroup> <Reference Include="System" /> <Reference Include="System.Data" /> <Reference Include="System.Deployment" /> <Reference Include="System.Xml" /> <Reference Include="System.Core" /> <Reference Include="System.Xml.Linq" /> <Reference Include="System.Data.DataSetExtensions" /> </ItemGroup> <ItemGroup> <Import Include="Microsoft.VisualBasic" /> <Import Include="System" /> <Import Include="System.Collections" /> <Import Include="System.Collections.Generic" /> <Import Include="System.Data" /> <Import Include="System.Diagnostics" /> <Import Include="System.Linq" /> <Import Include="System.Xml.Linq" /> </ItemGroup> <ItemGroup> <Compile Include="Module1.vb" /> <Compile Include="My Project\AssemblyInfo.vb" /> <Compile Include="My Project\Application.Designer.vb"> <AutoGen>True</AutoGen> <DependentUpon>Application.myapp</DependentUpon> </Compile> <Compile Include="My Project\Resources.Designer.vb"> <AutoGen>True</AutoGen> <DesignTime>True</DesignTime> <DependentUpon>Resources.resx</DependentUpon> </Compile> <Compile Include="My Project\Settings.Designer.vb"> <AutoGen>True</AutoGen> <DependentUpon>Settings.settings</DependentUpon> <DesignTimeSharedInput>True</DesignTimeSharedInput> </Compile> </ItemGroup> <ItemGroup> <EmbeddedResource Include="My Project\Resources.resx"> <Generator>VbMyResourcesResXFileCodeGenerator</Generator> <LastGenOutput>Resources.Designer.vb</LastGenOutput> <CustomToolNamespace>My.Resources</CustomToolNamespace> <SubType>Designer</SubType> </EmbeddedResource> </ItemGroup> <ItemGroup> <None Include="My Project\Application.myapp"> <Generator>MyApplicationCodeGenerator</Generator> <LastGenOutput>Application.Designer.vb</LastGenOutput> </None> <None Include="My Project\Settings.settings"> <Generator>SettingsSingleFileGenerator</Generator> <CustomToolNamespace>My</CustomToolNamespace> <LastGenOutput>Settings.Designer.vb</LastGenOutput> </None> </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.VisualBasic.targets" /> </Project>
|
When working with
MSBuild, you often manually create project files. Visual Studio 2010 IDE
offers a powerful Xml editor that is optimal for this purpose. To take
advantage of the Xml editor and IntelliSense feature, create a new Xml
file from Visual Studio and add a Project node like the one shown in Listing 51.1,
ensuring that the Xml schema for MSBuild is referred. This enables
IntelliSense and you can better understand all available members for
each particular section and subsection in the project file.
|
Project files are composed of different Xml sections. The first you encounter are ItemGroup and PropertyGroup, discussed in next sections.
Understanding ItemGroup Sections
You include files and contents in the build process within ItemGroup
sections, each representing a particular action. For example, the
following snippet shows how to include a code file in the build process
specifying that it must be compiled via the Compile element:
<ItemGroup>
<Compile Include="Module1.vb" />
</ItemGroup>
The next snippet shows how to add a reference to an external assembly:
<ItemGroup>
<Reference Include="System.Core" />
</ItemGroup>
The following snippet instead shows how to specify an Imports directive against the specified namespace:
<ItemGroup>
<Import Include="Microsoft.VisualBasic" />
</ItemGroup>
Notice how you can also embed resource files, specifying the .resx file, the associated code-behind file, and namespace:
<ItemGroup>
<EmbeddedResource Include="My Project\Resources.resx">
<Generator>VbMyResourcesResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.vb</LastGenOutput>
<CustomToolNamespace>My.Resources</CustomToolNamespace>
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
Then you can also add files to the project without assigning build actions, using the None element as follows:
<ItemGroup>
<None Include="My Project\Application.myapp">
<Generator>MyApplicationCodeGenerator</Generator>
<LastGenOutput>Application.Designer.vb</LastGenOutput>
</None>
</ItemGroup>
Understanding PropertyGroup Sections
When MSBuild builds project,
it needs some properties such as the output configuration, output type,
target assembly name, and so on. All such properties are stored within PropertyGroup nodes inside the project file and are basically key/value pairs. If you take a look at these elements in Listing 51.1, you notice how they are self-explanatory. For example, the following excerpt specifies properties for the output assembly:
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProductVersion>
</ProductVersion>
<SchemaVersion>
</SchemaVersion>
<ProjectGuid>{A30BC355-A5C8-4C56-97F0-C1A4F0A58222}</ProjectGuid>
<OutputType>Exe</OutputType>
<StartupObject>ConsoleApplication1.Module1</StartupObject>
<RootNamespace>ConsoleApplication1</RootNamespace>
<AssemblyName>ConsoleApplication1</AssemblyName>
<FileAlignment>512</FileAlignment>
<MyType>Console</MyType>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<TargetFrameworkProfile>Client</TargetFrameworkProfile>
</PropertyGroup>
Also look at Listing 51.1
to understand how properties map application options that generally you
set in Visual Studio via My Project, for example the VB Defaults (Option Strict, Option Explicit,
and so on). Thus properties are fundamental items required to tell
MSBuild how it must build the executable. You can also provide custom
properties. For example, the following section defines an Xml element
named DataFolder that declares a property named MyDataDirectory,
which is used later for demonstrating MSBuild targets but that is an
example of how you store custom information inside property groups:
<PropertyGroup>
<DataFolder>MyDataDirectory</DataFolder>
</PropertyGroup>
No matter where you place this PropertyGroup node in the Xml project file, just take care to write it as a child node of Project.