教程:使用 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 工具,然后选择“安装”

安装 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 格式的文件,其根节点 Project

大多数 .NET 项目都有一个 Sdk 属性。 这些项目称为 SDK 样式项目。 引用 SDK 意味着 MSBuild 导入一组文件,这些文件为该 SDK 提供生成基础结构。 如果未引用任何 SDK,仍可使用 MSBuild,则不会自动拥有所有特定于 SDK 的属性和目标。

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

出于特殊目的,.NET SDK 有多种变体:.NET Project SDK中介绍了它们。

生成应用程序的工作是通过 TargetTask 元素完成的。

  • 任务是最小的工作单位,换句话说,是构建的“原子”。 任务是独立的可执行组件,可以有输入和输出。 项目文件中当前未引用或定义任何任务。 您可以在以下部分中将任务添加到项目文件中。 有关详细信息,请参阅 任务

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

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

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

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

在 SDK 样式项目中,看不到此导入元素,因为 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 任务将 Text 属性的字符串值作为输入,并在输出设备上显示它(或者将其写入一个或多个日志(如果适用)。 HelloWorld 目标执行消息任务两次:首先显示“Hello”,然后显示“World”。

生成目标

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

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

说明

我们将在以下部分中将 开发人员命令提示符 称为 命令窗口

生成目标:

  1. 打开 命令窗口

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

    如果需要手动查找该文件,该文件 LaunchDevCmd.bat{Visual Studio 安装文件夹}\Common7\Tools 文件夹中。

  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>net8.0</TargetFrameworkVersion>

定义名为 TargetFrameworkVersion的属性,为其提供字符串值“net8.0”

可以随时重新定义构建属性。 如果

<TargetFrameworkVersion>net6.0</TargetFrameworkVersion>

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

检查属性值

若要获取属性的值,请使用以下语法,其中 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 属性显示在属性元素中。 仅当条件的计算结果为“true”时,才定义或重新定义条件属性。为未定义属性提供空字符串的默认值。 例如

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

表示“如果尚未定义 Configuration 属性,请定义它并为其指定值”Debug”。

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

预留属性

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

环境变量

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

从命令行设置属性

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

若要从命令行设置属性值:

  1. 命令窗口中,输入并执行以下行:

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

    Configuration is Release.
    

MSBuild 将创建 Configuration 属性,并为其提供值“Release”。

特殊字符

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

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

若要在消息任务中使用特殊字符:

  1. 在代码编辑器中,将这两个消息任务替换为以下行:

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

  3. 命令窗口中,输入并执行以下行:

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

    $(Configuration) is "Debug"
    

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

生成项

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

所有项都是 ItemGroup 元素的子元素。 项名称是子元素的名称,项值是子元素的 Include 属性的值。 具有相同名称的项值将收集到该名称的项类型中。 例如

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

定义包含两个项的项组。 项类型 Compile 有两个值:Program.csProperties\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 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" />

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

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

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

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

将所有具有文件扩展名 .cs 的文件添加到 Compile 项类型中,但文件名中包含字符串 Designer的文件除外。 若要了解更多示例,请参阅如何:从生成中排除文件

在包含 ExcludeInclude 属性的项元素中,Exclude 属性仅影响由 Include 属性添加的项。 例如

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

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

包含和排除项

  1. 在代码编辑器中,将消息任务替换为以下行:

    <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 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.DependentUpon”会多次出现。 在目标中使用此语法的元数据会导致“批处理”。批处理意味着针对每个唯一元数据值执行目标中的任务一次。 批处理是与常见的“foreach 循环”编程构造等效的 MSBuild 脚本。 有关详细信息,请参阅批处理

已知元数据

每当项添加到项列表中时,该项将分配一些已知元数据。 例如,%(Filename) 返回任何项的文件名。 有关已知元数据的完整列表,请参阅 已知项元数据

检查常见元数据:

  1. 在代码编辑器中,将消息任务替换为以下行:

    <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
    

通过比较上述两个示例,可以看到,虽然 Compile 项类型中的每个项都没有 DependentUpon 元数据,但所有项都具有已知的 Filename 元数据。

元数据转换

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

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

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

使用元数据转换项:

  1. 在代码编辑器中,将消息任务替换为以下行:

    <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 参考