演练:使用 MSBuild
MSBuild 是 Microsoft 和 Visual Studio 的生成平台。 本演练介绍 MSBuild 的生成块,并演示如何编写、操作和调试 MSBuild 项目。 学习内容:
创建和操作项目文件。
如何使用生成属性
如何使用生成项。
可以从 Visual Studio 或命令窗口中运行 MSBuild。 在本演练中,将使用 Visual Studio 创建 MSBuild 项目文件。 您将在 Visual Studio 中编辑项目文件,并使用命令窗口生成项目,然后检查结果。
创建 MSBuild 项目
Visual Studio 项目系统以 MSBuild 为基础。 因此,可以轻松地使用 Visual Studio 创建新项目文件。 在本节中,将创建一个 Visual C# 项目文件。 可以选择改为创建 Visual Basic 项目文件。 在此演练的上下文中,这两种项目文件的差别很小。
创建项目文件
打开 Visual Studio。
在**“文件”菜单上指向“新建”,再单击“项目”**。
在**“新建项目”对话框中,选择 Visual C# 项目类型,再选择“Windows 窗体应用程序”模板。 在“名称”框中键入 BuildApp。 输入解决方案的“位置”,例如,D:\。 接受以下各项的默认值:“创建解决方案的目录”(选定)、“添加到源代码管理”(未选定)和“解决方案名称”**(BuildApp)。
单击**“确定”**创建项目文件。
检查项目文件
上一节中使用了 Visual Studio 创建 Visual C# 项目文件。 项目文件在**“解决方案资源管理器”**中由名为 BuildApp 的项目节点表示。 您可以使用 Visual Studio 代码编辑器来检查项目文件。
检查项目文件
在**“解决方案资源管理器”**中,单击项目节点 BuildApp。
在**“属性”浏览器中,请注意,“项目文件”**属性为 BuildApp.csproj。 所有项目文件在命名时均带有后缀“proj”。 如果创建了 Visual Basic 项目,则项目文件名将为 BuildApp.vbproj。
右击项目节点,然后单击**“卸载项目”**。
再次右击该项目节点,然后单击**“编辑 BuildApp.csproj”**。
此时,该项目文件将出现在代码编辑器中。
目标和任务
项目文件是 XML 格式的文件,其根节点为 Project。
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="https://schemas.microsoft.com/developer/msbuild/2003">
必须在 Project 元素中指定 xmlns 命名空间。
生成和应用程序的工作是通过 Target 和 Task 元素完成的。
任务是最小的工作单元,换言之,即生成的“原子”。 任务是可能具有输入和输出的独立可执行组件。 项目文件中当前没有引用或定义任务。 您将在以下各节中将任务添加到项目文件。 有关更多信息,请参见主题MSBuild 任务。
目标是指定的一系列任务。 项目文件结尾处有两个目标,这些目标当前括在 HTML 注释 BeforeBuild 和 AfterBuild 中。
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
有关更多信息,请参见主题MSBuild 目标。
Project 节点具有可选的 DefaultTargets 特性,用于选择要生成的默认目标,在本例中为 Build。
<Project ToolsVersion="4.0" DefaultTargets="Build" ...
Build 目标不是在项目文件中定义的, 而是使用 Import 元素从文件 Microsoft.CSharp.targets 中导入的。
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
导入的文件会实际插入到文件项目文件中引用了它们的任何地方。
MSBuild 将跟踪生成的目标,并确保每个目标只生成一次。
添加目标和任务
将目标添加到项目文件。 向目标中添加一个用于输出消息的任务。
添加目标和任务
将以下各行添加到项目文件,置于 Import 语句的后面:
<Target Name="HelloWorld"> </Target>
这会创建一个名为 HelloWorld 的目标。 请注意,在编辑项目文件时,支持使用 Intellisense。
向 HelloWorld 目标中添加几行,使生成的部分如下所示:
<Target Name="HelloWorld"> <Message Text="Hello"></Message> <Message Text="World"></Message> </Target>
保存项目文件。
Message 任务是 MSBuild 附带的许多任务之一。 有关可用任务和用法信息的完整列表,请参见 MSBuild 任务参考。
Message 任务接受 Text 特性的字符串值作为输入,并在输出设备上显示该值。 HelloWorld 目标执行 Message 任务两次:第一次显示“Hello”,然后显示“World”。
生成目标
从**“Visual Studio 命令提示”**中运行 MSBuild,生成上面定义的 HelloWorld 目标。 使用 /target 或 /t 命令行开关以选择目标。
提示
在以下各节中,我们将“Visual Studio 命令提示”称为“命令窗口”。
生成目标
单击**“开始”,然后单击“所有程序”。 在“Visual Studio 工具”文件夹中找到并单击“Visual Studio 命令提示”**。
从命令窗口导航到包含项目文件的文件夹,在本例中为 D:\BuildApp\BuildApp。
使用命令开关 /t:HelloWorld 运行 msbuild。 此操作将选择并生成 HelloWorld 目标:
msbuild helloworld.csproj /t:HelloWorld
在**“命令窗口”**中检查输出。 您应会看到“Hello”和“World”两行:
Hello
World
提示
如果看到的是 The target "HelloWorld" does not exist in the project ,则您可能忘记了在代码编辑器中保存项目文件。 请保存文件并重试。
通过交替显示代码编辑器和命令窗口,您可以更改项目文件并快速查看结果。
提示
如果不使用 /t 命令开关运行 msbuild,则 msbuild 将生成由 Project 元素的 DefaultTarget 特性指定的目标,在本例中为“Build”。 这将会生成 Windows 窗体应用程序 BuildApp.exe。
生成属性
生成属性是用于引导生成的名称/值对。 项目文件的顶部已定义了若干生成属性:
<PropertyGroup>
...
<ProductVersion>10.0.11107</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{30E3C9D5-FD86-4691-A331-80EA5BA7E571}</ProjectGuid>
<OutputType>WinExe</OutputType>
...
</PropertyGroup>
所有属性都是 PropertyGroup 元素的子元素。 属性的名称是子元素的名称,属性的值是子元素的文本元素。 例如,
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
定义名为 TargetFrameworkVersion 的属性,并为其指定字符串值“v4.0”。
生成属性可随时重新定义。 If
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
出现在项目文件的后面,或出现在项目文件中后面导入的文件内,则 TargetFrameworkVersion 将接受新值“v3.5”。
检查属性值
要获取属性的值,请使用以下语法,其中 PropertyName 是属性的名称:
$(PropertyName)
使用此语法来检查项目文件中的某些属性。
检查属性值
从代码编辑器中,用下面的代码替换 HelloWorld 目标:
<Target Name="HelloWorld"> <Message Text="Configuration is $(Configuration)" /> <Message Text="MSBuildToolsPath is $(MSBuildToolsPath)" /> </Target>
保存项目文件。
从**“命令窗口”**中,输入并执行以下行:
msbuild helloworld.csproj /t:HelloWorld
检查输出。 您应会看到以下两行(.NET Framework 版本可能不同):
Configuration is Debug
MSBuildToolsPath is C:\Windows\Microsoft.NET\Framework\v4.0.20317
提示
如果看不到这两行,则您可能忘记了在代码编辑器中保存项目文件。 请保存文件并重试。
条件属性
许多属性(例如 Configuration)都是有条件地定义的,也就是说,属性元素中会出现 Condition 特性。 只有当条件的计算结果为“true”时,才会定义或重新定义条件属性。 请注意,将为未定义的属性指定默认值,即空字符串。 例如,
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
意指“如果尚未定义 Configuration 属性,则定义该属性并为其指定值‘Debug’”。
几乎所有 MSBuild 元素都可以有 Condition 特性。 有关使用 Condition 特性的更多讨论,请参见 MSBuild 条件。
保留属性
MSBuild 保留了一些属性名称,用于存储有关项目文件和 MSBuild 二进制文件的信息。 例如,MSBuildToolsPath 就是一个保留属性(MSBuild 3.5 中新增)。 与任何其他属性一样,保留属性是使用 $ 表示法引用的。 有关更多信息,请参见如何:引用项目文件的名称或位置和 MSBuild 保留属性。
环境变量
可以采用与生成属性相同的方式,在项目文件中引用环境变量。 例如,若要在项目文件中使用 PATH 环境变量,请使用 $(Path)。 如果项目包含一个与环境变量同名的属性定义,则此属性在项目中重写环境变量的值。 有关更多信息,请参见如何:在生成中使用环境变量。
从命令行设置属性
可以使用 /property 或 /p 命令行开关在命令行上定义属性。 从命令行接收的属性值会替代在项目文件和环境变量中设置的属性值。
从命令行设置属性值
从**“命令窗口”**中,输入并执行以下行:
msbuild helloworld.csproj /t:HelloWorld /p:Configuration=Release
检查输出。 您应会看到以下行:
Configuration is Release.
MSBuild 将创建 Configuration 属性,并为其指定值“Release”。
特殊字符
某些字符在 MSBuild 项目文件中具有特殊含义。 这些字符的示例包括分号 (;) 和星号 (*)。 要在项目文件中使用这些特殊字符作为原义字符,必须使用语法 %xx 指定这些字符,其中 xx 表示字符的 ASCII 十六进制值。
更改 Message 任务,用特殊字符显示 Configuration 属性的值,以使其更具可读性。
在 Message 任务中使用特殊字符
从代码编辑器中,用以下行替换两个 Message 任务:
<Message Text="%24(Configuration) is %22$(Configuration)%22" />
保存项目文件。
从**“命令窗口”**中,输入并执行以下行:
msbuild helloworld.csproj /t:HelloWorld
检查输出。 您应会看到以下行:
$(Configuration) is "Debug"
有关更多信息,请参见 MSBuild 特殊字符。
生成项
项是一段信息,通常为用作生成系统输入的文件名。 例如,可以将表示源文件的项集合传递到名为 Compile 的任务,以将它们编译到程序集中。
所有项都是 ItemGroup 元素的子元素。 项名称为子元素的名称,项值为子元素的 Include 特性的值。 同名项的值将收集到该名称的项类型中。 例如,
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
定义包含两个项的项组。 项类型 Compile 具有两个值:“Program.cs”和“Properties\AssemblyInfo.cs”。
下面的代码通过在一个 Include 特性中同时声明两个文件(用分号分隔),从而创建相同的项类型。
<ItemGroup>
<Compile Include="Program.cs;Properties\AssemblyInfo.cs" />
</ItemGroup>
有关更多信息,请参见 MSBuild 项。
提示
文件路径与包含 MSBuild 项目文件的文件夹相关。
检查项类型值
要获取项类型的值,请使用以下语法,其中 ItemType 是项类型的名称:
@(ItemType)
使用此语法来检查项目文件中的 Compile 项类型。
检查项类型值
从代码编辑器中,用下面的代码替换 HelloWorld 目标任务:
<Target Name="HelloWorld"> <Message Text="Compile item type contains @(Compile)" /> </Target>
保存项目文件。
从**“命令窗口”**中,输入并执行以下行:
msbuild helloworld.csproj /t:HelloWorld
检查输出。 您应会看到以下较长的行:
Compile item type contains Form1.cs;Form1.Designer.cs;Program.cs;Properties\AssemblyInfo.cs;Properties\Resources.Designer.cs;Properties\Settings.Designer.cs
默认情况下,项类型的各个值由分号分隔。
要更改项类型的分隔符,请使用以下语法,其中 ItemType 是项类型,Separator 是由一个或多个分隔字符组成的字符串:
@(ItemType, Separator)
更改 Message 任务,以便使用回车符和换行符 (%0A%0D) 每行显示一个 Compile 项。
每行显示一个项类型值
从代码编辑器中,用以下行替换 Message 任务:
<Message Text="Compile item type contains @(Compile, '%0A%0D')" />
保存项目文件。
从**“命令窗口”**中,输入并执行以下行:
msbuild helloworld.csproj /t:HelloWorld
检查输出。 您应会看到以下各行:
Compile item type contains Form1.cs
Form1.Designer.cs
Program.cs
Properties\AssemblyInfo.cs
Properties\Resources.Designer.cs
Properties\Settings.Designer.cs
Include、Exclude 和通配符
可将通配符“*”、“**”和“?”与 Include 特性结合使用来向项类型中添加项。 例如,
<Photos Include="images\*.jpeg" />
将 images 文件夹中文件扩展名为“.jpeg”的所有文件添加到 Photos 项类型,而
<Photos Include="images\**.jpeg" />
将 images 文件夹及其所有子文件夹中文件扩展名为“.jpeg”的所有文件添加到 Photos 项类型。 有关更多示例,请参见如何:选择要生成的文件。
请注意,在声明项时,会将项添加到项类型。 例如,
<Photos Include="images\*.jpeg" />
<Photos Include="images\*.gif" />
创建一个名为 Photo 的项类型,其中包含 images 文件夹中文件扩展名为“.jpeg”或“.gif”的所有文件。 这与以下行等效:
<Photos Include="images\*.jpeg;images\*.gif" />
可以使用 Exclude 特性,从项类型中排除某一项。 例如,
<Compile Include="*.cs" Exclude="*Designer*">
将文件扩展名为“.cs”的所有文件添加到 Compile 项类型,名称中包含字符串“Designer”的文件除外。 有关更多示例,请参见如何:将文件排除在生成过程外。
在同时包含 Exclude 特性和 Include 特性的项元素中,Exclude 特性只会影响通过 Include 特性添加的项。 例如,
<Compile Include="*.cs" />
<Compile Include="*.res" Exclude="Form1.cs">
不排除在前面的项元素中添加的文件 Form1.cs。
包括和排除项
从代码编辑器中,用以下行替换 Message 任务:
<Message Text="Compile item type contains @(XFiles)" />
添加以下项组,置于 Import 元素的后面:
<ItemGroup> <XFiles Include="*.cs;properties/*.resx" Exclude="*Designer*" /> </ItemGroup>
保存项目文件。
从**“命令窗口”**中,输入并执行以下行:
msbuild helloworld.csproj /t:HelloWorld
检查输出。 您应会看到以下行:
Compile item type contains Form1.cs;Program.cs;Properties/Resources.resx
项的元数据
除了通过 Include 和 Exclude 特性收集的信息外,项还可包含元数据。 当任务需要有关项的更多信息,而不仅仅是项值时,可以使用此元数据。
项元数据是通过创建作为该项的子元素的具有元数据名称的元素在项目文件中声明的。 项可以有零个或多个元数据值。 例如,以下 CSFile 项具有值为“Fr”的 Culture 元数据:
<ItemGroup>
<CSFile Include="main.cs">
<Culture>Fr</Culture>
</CSFile>
</ItemGroup>
要获取项类型的元数据值,请使用以下语法,其中 ItemType 是项类型的名称,MetaDataName 是元数据的名称:
%(ItemType.MetaDataName)
检查项元数据
从代码编辑器中,用以下行替换 Message 任务:
<Message Text="Compile.DependentUpon: %(Compile.DependentUpon)" />
保存项目文件。
从**“命令窗口”**中,输入并执行以下行:
msbuild helloworld.csproj /t:HelloWorld
检查输出。 您应会看到以下各行:
Compile.DependentUpon:
Compile.DependentUpon: Form1.cs
Compile.DependentUpon: Resources.resx
Compile.DependentUpon: Settings.settings
请注意,词组“Compile.DependentUpon”出现了多次。 如果在目标内通过此语法使用元数据,将会导致“批处理”。 批处理是指为每个唯一的元数据值都执行一次目标内的任务。 这是常见“for loop”编程构造的 MSBuild 脚本等效项。 有关更多信息,请参见MSBuild 批处理。
已知的元数据
每当将项添加到项列表时,都会将某些已知的元数据赋值给该项。 例如,%(Filename) 将返回任意项的文件名。 有关已知元数据的完整列表,请参见 MSBuild 常见的项元数据。
检查已知的元数据
从代码编辑器中,用以下行替换 Message 任务:
<Message Text="Compile Filename: %(Compile.Filename)" />
保存项目文件。
从**“命令窗口”**中,输入并执行以下行:
msbuild helloworld.csproj /t:HelloWorld
检查输出。 您应会看到以下各行:
Compile Filename: Form1
Compile Filename: Form1.Designer
Compile Filename: Program
Compile Filename: AssemblyInfo
Compile Filename: Resources.Designer
Compile Filename: Settings.Designer
通过比较上面的两个示例,您可以看到,尽管并非 Compile 项类型中的每个项都具有 DependentUpon 元数据,但所有项都具有已知的 Filename 元数据。
元数据转换
可以将项列表转换为新的项列表。 要转换项列表,请使用以下语法,其中 ItemType 是项类型的名称,MetadataName 是元数据的名称:
@(ItemType -> '%(MetadataName)')
例如,通过使用类似于 @(SourceFiles -> '%(Filename).obj') 的表达式,可以将源文件的项列表转换为对象文件的集合。 有关更多信息,请参见 MSBuild 转换。
使用元数据转换项
从代码编辑器中,用以下行替换 Message 任务:
<Message Text="Backup files: @(Compile->'%(filename).bak')" />
保存项目文件。
从**“命令窗口”**中,输入并执行以下行:
msbuild helloworld.csproj /t:HelloWorld
检查输出。 您应会看到以下行:
Backup files: Form1.bak;Form1.Designer.bak;Program.bak;AssemblyInfo.bak;Resources.Designer.bak;Settings.Designer.bak
请注意,用此语法表示的元数据不会导致批处理。
接下来的内容
要了解如何一次一个步骤地创建简单的项目文件,请尝试演练:从头开始创建 MSBuild 项目文件。