教程:使用 MSBuild

MSBuild 是 Microsoft 和 Visual Studio 的生成平台。 本教程介绍 MSBuild 的构建基块,并演示如何编写、操作和调试 MSBuild 项目。 学习内容:

  • 创建和操作的项目文件。

  • 如何使用生成属性。

  • 如何使用生成项。

可从 Visual Studio 或命令窗口 运行 MSBuild。 本教程将使用 Visual Studio 创建 MSBuild 项目文件。 可在 Visual Studio 中编辑项目文件,并使用命令窗口 生成项目并检查结果。

安装 MSBuild

如果具有 Visual Studio,表示已经安装了 MSBuild。 在 Visual Studio 2019 及更高版本中,它安装在 Visual Studio 安装文件夹下。 对于 Windows 10 上的典型默认安装,MSBuild.exe 位于 MSBuild\Current\Bin 中的安装文件夹下。

在安装程序中,确保选择要为工作负载使用的 MSBuild 工具,然后选择“安装”。

Installing MSBuild

若要在没有 Visual Studio 的系统上安装 MSBuild,请转到 Visual Studio 2019 生成工具,或安装 .NET SDK

如果具有 Visual Studio,表示已经安装了 MSBuild。 在 Visual Studio 2022 中,它安装在 Visual Studio 安装文件夹下。 对于 Windows 10 上的典型默认安装,MSBuild.exe 位于 MSBuild\Current\Bin 中的安装文件夹下。

在 Visual Studio 安装程序中,导航到“各个组件”,并找到“MSBuild”。 当你选择安装任何其他工作负载时,会自动选择它。

若要在没有 Visual Studio 的系统上安装 MSBuild,请转到下载页面上的 Visual Studio 2022 生成工具。 另一种安装 MSBuild 的方式是安装 .NET SDK

创建 MSBuild 项目

Visual Studio 项目系统以 MSBuild 为基础。 使用 Visual Studio 创建新的项目文件很容易。 在本部分中,你将创建 C# 项目文件。 可选择改为创建 Visual Basic 项目文件。 在本教程的上下文中,两种项目文件间的差异很小。

创建项目文件

  1. 打开 Visual Studio 并创建一个项目:

    在搜索框中,键入 winforms,然后选择“创建一个新的 Windows 窗体应用(.NET Framework)”。 在出现的对话框中,选择“创建” 。

    在“项目名称”框中,键入 BuildApp。 输入解决方案的“位置”,例如 D:\。

  2. 单击“确定” 或“创建” 以创建项目文件。

检查项目文件

在上一部分中,你使用 Visual Studio 创建了一个 C# 项目文件。 该项目文件在解决方案资源管理器中由名为 BuildApp 的项目节点表示。 可使用 Visual Studio 代码编辑器来检查项目文件。

检查项目文件

  1. 在“解决方案资源管理器” 中,单击项目节点“BuildApp” 。

  2. 请注意,在“属性” 浏览器中,“项目文件” 属性是“BuildApp.csproj” 。 所有项目文件名称中都带有后缀“proj” 。 如果创建了 Visual Basic 项目,则项目文件名称将为“BuildApp.vbproj” 。

  3. 再次右键单击项目节点,然后单击“编辑 BuildApp.csproj” 。

    该项目文件出现在代码编辑器中。

注意

对于某些项目类型(例如 C++),需要卸载项目(右键单击项目文件,然后选择“卸载项目”)才能打开和编辑项目文件 。

目标和任务

项目文件是 XML 格式的文件,带有根节点项目

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

大多数 .NET 项目都具有 Sdk 属性。 这些项目称为 SDK 样式的项目。

<Project Sdk="Microsoft.NET.Sdk">

有许多用于特殊目的的 .NET SDK 的变体;.NET 项目 SDK 中对它们进行了介绍。

生成应用程序的工作由 TargetTask 元素完成。

  • 任务是工作的最小单位,换言之,它是生成的“原子”。 任务是独立的可执行组件,可以有输入和输出。 目前尚没有在项目文件中引用或定义的任务。 在以下各部分中,将项目添加到项目文件。 有关详细信息,请参阅任务

  • 目标是任务的已命名序列。 它可以是一个已命名的任务序列,但实际上,它表示要生成或完成的内容,因此应以面向目标的方式进行定义。 有关详细信息,请参阅目标

默认目标不是在项目文件中定义的。 而是在导入的项目中指定的。 Import 元素可指定导入的项目。 例如在 C# 项目中,默认目标是从“Microsoft.CSharp.targets” 文件中导入的。

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

无论是否使用导入的文件,都会将其有效插入项目文件。

在 SDK 样式的项目中,不会显示此 Import 元素,因为 SDK 特性会导致此文件被隐式导入。

MSBuild 跟踪生成的目标,并保证每个目标生成次数不超过一次。

添加目标和任务

将目标添加到项目文件。 将任务添加到打印消息的目标。

添加目标和任务

  1. 将这些行添加到项目文件中,具体位于 Import 语句或打开的 Project 元素之后。

    <Target Name="HelloWorld">
    </Target>
    

    此代码创建名为 HelloWorld 的目标。 请注意,编辑项目文件时会拥有 IntelliSense 支持。

  2. 将行添加到 HelloWorld 目标,以便生成如下所示的结果:

    <Target Name="HelloWorld">
      <Message Text="Hello"></Message>  <Message Text="World"></Message>
    </Target>
    
  3. 保存项目文件。

Message 任务是 MSBuild 所附带的许多任务之一。 有关可用任务的完整列表以及用法信息,请参阅任务参考

Message 任务将文本属性的字符串值作为输入并显示在输出设备上(或者如果适用,将其写入一个或多个日志)。 HelloWorld 目标执行 Message 任务两次:第一次显示“Hello”,第二次显示“World”。

生成目标

如果尝试从 Visual Studio 生成此项目,它不生成定义的目标。 这是因为 Visual Studio 选择了默认目标,该目标仍是导入的 .targets 文件中的目标。

从 Visual Studio 的“开发人员命令提示”运行 MSBuild,生成前面定义的 HelloWorld 目标。 使用 -target 或 -t 命令行开关选择目标。

注意

在以下各部分中,将“开发人员命令提示”称为“命令窗口”

生成目标:

  1. 打开“命令窗口” 。

    在任务栏的搜索框中,开始键入工具的名称,例如 devdeveloper command prompt。 然后显示一个列表,其中包含与搜索模式匹配的已安装应用。

    如需手动查找,可在 {Visual Studio installation folder}\Common7\Tools 文件夹中找到 LaunchDevCmd.bat 文件

  2. 从命令窗口导航到包含项目文件的文件夹,此例中为 D:\BuildApp\BuildApp 。

  3. 使用命令开关 -t:HelloWorld 运行 msbuild。 此命令选择并生成 HelloWorld 目标:

    msbuild buildapp.csproj -t:HelloWorld
    
  4. 在“命令窗口” 检查输出。 应看到两行“Hello”和“World”:

    Hello
    World
    

注意

如果出现的是 The target "HelloWorld" does not exist in the project,则可能是忘记将项目文件保存到代码编辑器中。 请保存文件并重试。

通过在代码编辑器和命令窗口之间进行交替,可更改项目文件并快速查看结果。

生成属性

生成属性是引导生成的名称/值对。 已在项目文件顶部定义了几个生成属性:

<PropertyGroup>
...
  <ProductVersion>10.0.11107</ProductVersion>
  <SchemaVersion>2.0</SchemaVersion>
  <ProjectGuid>{30E3C9D5-FD86-4691-A331-80EA5BA7E571}</ProjectGuid>
  <OutputType>WinExe</OutputType>
...
</PropertyGroup>

所有属性都是 PropertyGroup 元素的子元素。 属性的名称是子元素的名称,属性的值是子元素的文本元素。 例如,

<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>

定义名为 TargetFrameworkVersion 的属性,并为其指定字符串值“v4.5”。

可能会随时重新定义生成属性。 如果

<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>

稍后出现在项目文件中,或稍后出现在项目文件中的导入文件中,则 TargetFrameworkVersion 会采用新值“v3.5”。

检查属性值

若要获取属性值,请使用以下语法,其中 PropertyName 是属性的名称:

$(PropertyName)

使用此语法可检查项目文件中的一些属性。

检查属性值

  1. 从代码编辑器中使用以下代码替换 HelloWorld 目标:

    <Target Name="HelloWorld">
      <Message Text="Configuration is $(Configuration)" />
      <Message Text="MSBuildToolsPath is $(MSBuildToolsPath)" />
    </Target>
    
  2. 保存项目文件。

  3. 在“命令窗口” 输入并执行此行:

    msbuild buildapp.csproj -t:HelloWorld
    
  4. 检查输出。 应看到这两行(输出可能不同):

    Configuration is Debug
    MSBuildToolsPath is C:\Program Files\Microsoft Visual Studio\2022\MSBuild\Current\Bin\amd64
    
    Configuration is Debug
    MSBuildToolsPath is C:\Program Files (x86)\Microsoft Visual Studio\2019\MSBuild\16.0\Bin
    

条件属性

许多属性(如 Configuration)都是按条件进行定义的,也就是说,Condition 属性出现在 property 元素中。 仅当条件评估结果为“true”时才定义或重新定义条件属性。向未定义的属性给定空字符串的默认值。 例如,

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

表示“如果尚未定义配置属性,请定义该属性并为其指定‘Debug’值”。

几乎所有 MSBuild 元素都可以具有条件属性。 有关使用条件属性的详细讨论,请参阅条件

预留属性

MSBuild 保留了一些属性名称,用于存储有关项目文件和 MSBuild 二进制文件的信息。 MSBuildToolsPath 就是保留属性的一个示例。 与其他属性一样,可使用 $ 符号引用保留属性。 有关详细信息,请参阅如何:引用项目文件的名称或位置MSBuild 保留和常见属性

环境变量

可使用与生成属性相同的方式引用项目文件中的环境变量。 例如,若要使用项目文件中的 PATH 环境变量,可使用 $(Path)。 如果项目包含与环境变量具有相同名称的属性定义,则项目中的属性将替代环境变量的值。 有关详细信息,请参阅如何:在生成中使用环境变量

从命令行设置属性

可使用 -property 或 -p 命令行开关在命令行中定义属性。 从命令行接收的属性值将替代在项目文件和环境变量中设置的属性值。

在命令行中设置属性值:

  1. 在“命令窗口” 输入并执行此行:

    msbuild buildapp.csproj -t:HelloWorld -p:Configuration=Release
    
  2. 检查输出。 应看到此行:

    Configuration is Release.
    

MSBuild 创建配置属性并赋予其“发布”值。

特殊字符

某些字符在 MSBuild 项目文件中具有特殊意义。 这些字符的示例包括分号 (;) 和星号 (*)。 要将这些特殊字符用作项目文件中的文本,必须使用语法 %<xx> 来指定它们,其中 <xx> 表示字符的 ASCII 十六进制值。

更改 Message 任务以显示具有特殊字符的配置属性的值,使其更易读。

在 Message 任务中使用特殊字符:

  1. 从代码编辑器中使用此行替换这两个 Message 任务:

    <Message Text="%24(Configuration) is %22$(Configuration)%22" />
    
  2. 保存项目文件。

  3. 在“命令窗口” 输入并执行此行:

    msbuild buildapp.csproj -t:HelloWorld
    
  4. 检查输出。 应看到此行:

    $(Configuration) is "Debug"
    

有关详细信息,请参阅 MSBuild 特殊字符

生成项

项是一段信息,通常是文件名,用作生成系统的输入。 例如,表示源文件的项集合可能会传递到名为“Compile”的任务,以将它们编译为程序集。

所有项都是 ItemGroup 元素的子元素。 项名称是子元素的名称,项值是子元素的包含属性的值。 具有相同名称的项值将收集到该名称的项类型中。 例如,应用于对象的

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

定义包含两个项的项组。 项类型编译有两个值:Program.cs 和 Properties\AssemblyInfo.cs 。

以下代码通过在一个 Include 属性中声明这两个文件(用分号分隔)来创建相同的项目类型。

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

有关详细信息,请参阅

注意

文件路径是包含 MSBuild 项目文件的文件夹的相对路径,即使项目文件是导入的项目文件也是如此。 但存在一些例外情况,例如,在使用 ImportUsingTask 元素时。

检查项类型值

若要获取项目类型的值,请使用以下语法,其中 ItemType 是项目类型的名称:

@(ItemType)

使用此语法可检查项目文件中的 Compile 项目类型。

检查项类型值:

  1. 从代码编辑器中使用以下代码替换 HelloWorld 目标任务:

    <Target Name="HelloWorld">
      <Message Text="Compile item type contains @(Compile)" />
    </Target>
    
  2. 保存项目文件。

  3. 在“命令窗口” 输入并执行此行:

    msbuild buildapp.csproj -t:HelloWorld
    
  4. 检查输出。 应看到这一长行:

    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) 显示每行的编译项。

每行显示一个项类型值

  1. 从代码编辑器中使用此行替换 Message 任务:

    <Message Text="Compile item type contains @(Compile, '%0A%0D')" />
    
  2. 保存项目文件。

  3. 在“命令窗口” 输入并执行此行:

    msbuild buildapp.csproj -t:HelloWorld
    
  4. 检查输出。 应看到这些行:

    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" />

将“图片” 文件夹中所有文件扩展名为“.jpeg” 的文件添加到照片项类型,而

<Photos Include="images\**\*.jpeg" />

将“图片” 文件夹和其所有子文件夹中所有文件扩展名为“.jpeg” 的文件添加到照片项类型。 有关更多示例,请参见如何:选择要生成的文件

注意,项在声明时会被添加到项类型。 例如,

<Photos Include="images\*.jpeg" />
<Photos Include="images\*.gif" />

创建一个名为“照片”的项类型,其中包含“图片”文件夹中的所有文件,这些文件的扩展名为 .jpeg.gif。 这些行等效于以下行:

<Photos Include="images\*.jpeg;images\*.gif" />

可使用 Exclude 属性从项类型中排除项。 例如,

<Compile Include="*.cs" Exclude="*Designer*">

将所有文件扩展名为“.cs” 的文件添加到编译项类型,除了名称中包含字符串“Designer” 的文件。 有关更多示例,请参见如何:从生成中排除文件

Exclude 属性只会影响由 Include 属性添加的项(这两个属性均位于项元素中)。 例如,

<Compile Include="*.cs" />
<Compile Include="*.res" Exclude="Form1.cs">

不会排除在之前的项元素中添加的文件 Form1.cs

包含和排除项

  1. 从代码编辑器中使用此行替换 Message 任务:

    <Message Text="XFiles item type contains @(XFiles)" />
    
  2. 在 Import 元素后添加此项组:

    <ItemGroup>
       <XFiles Include="*.cs;properties/*.resx" Exclude="*Designer*" />
    </ItemGroup>
    
  3. 保存项目文件。

  4. 在“命令窗口” 输入并执行此行:

    msbuild buildapp.csproj -t:HelloWorld
    
  5. 检查输出。 应看到此行:

    XFiles item type contains Form1.cs;Program.cs;Properties/Resources.resx
    

项元数据

除了从 IncludeExclude 属性搜集的信息外,项还可包含元数据。 需要关于项的详细信息而不仅仅是项值的任务可使用此元数据。

项元数据可通过创建元素(其中元数据的名称作为项的子元素)在项目文件中进行声明。 项可以有零个或零个以上的元数据值。 例如,以下 CSFile 项具有值为“Fr”的区域性元数据:

<ItemGroup>
    <CSFile Include="main.cs">
        <Culture>Fr</Culture>
    </CSFile>
</ItemGroup>

若要获取项目类型的元数据值,请使用以下语法,其中 ItemType 是项目类型的名称,MetaDataName 是元数据的名称:

%(ItemType.MetaDataName)

检查项元数据:

  1. 从代码编辑器中使用此行替换 Message 任务:

    <Message Text="Compile.DependentUpon: %(Compile.DependentUpon)" />
    
  2. 保存项目文件。

  3. 在“命令窗口” 输入并执行此行:

    msbuild buildapp.csproj -t:HelloWorld
    
  4. 检查输出。 应看到这些行:

    Compile.DependentUpon:
    Compile.DependentUpon: Form1.cs
    Compile.DependentUpon: Resources.resx
    Compile.DependentUpon: Settings.settings
    

请注意,短语“Compile.Dependent Upon”会多次出现。 在目标中使用具有此语法的元数据会造成“批处理”。批处理是指针对每个唯一元数据值执行一次目标中的任务。 批处理是等效于常见的“foreach 循环”编程结构的 MSBuild 脚本。 有关详细信息,请参阅批处理

常见元数据

无论何时向项列表添加项时,都会向该项分配一些常见元数据。 例如,%(Filename) 会返回任何项的文件名。 若要了解完整的常见元数据列表,请参阅常见项元数据

检查常见元数据:

  1. 从代码编辑器中使用此行替换 Message 任务:

    <Message Text="Compile Filename: %(Compile.Filename)" />
    
  2. 保存项目文件。

  3. 在“命令窗口” 输入并执行此行:

    msbuild buildapp.csproj -t:HelloWorld
    
  4. 检查输出。 应看到这些行:

    Compile Filename: Form1
    Compile Filename: Form1.Designer
    Compile Filename: Program
    Compile Filename: AssemblyInfo
    Compile Filename: Resources.Designer
    Compile Filename: Settings.Designer
    

通过比较前面两个示例,可以看到,虽然并非每个编译项类型中的项都有 DependentUpon 元数据,但所有项都具有常见 Filename 元数据。

元数据转换

项列表可以转换为新的项列表。 若要转换项列表,请使用以下语法,其中 <ItemType> 是项类型的名称,<MetadataName> 是元数据的名称:

@(ItemType -> '%(MetadataName)')

例如,可使用表达式 @(SourceFiles -> '%(Filename).obj') 将源文件的项列表转换为对象文件的集合。 有关详细信息,请参阅转换

使用元数据转换项:

  1. 从代码编辑器中使用此行替换 Message 任务:

    <Message Text="Backup files: @(Compile->'%(filename).bak')" />
    
  2. 保存项目文件。

  3. 在“命令窗口” 输入并执行此行:

    msbuild buildapp.csproj -t:HelloWorld
    
  4. 检查输出。 应看到此行:

    Backup files: Form1.bak;Form1.Designer.bak;Program.bak;AssemblyInfo.bak;Resources.Designer.bak;Settings.Designer.bak
    

请注意,此语法中表示的元数据不会造成批处理。

后续步骤

要了解如何在 Windows 上一步步创建一个简单的项目文件,请尝试从头开始创建 MSBuild 项目文件

如果你主要使用 .NET SDK,请继续阅读 .NET SDK 项目的 MSBuild 参考