从头开始创建 MSBuild 项目文件

面向 .NET Framework 的编程语言使用 MSBuild 项目文件来描述和控制应用程序生成过程。 使用 Visual Studio 创建 MSBuild 项目文件时,会自动将相应的 XML 添加到该文件。 但是,你可能会发现,了解 XML 的组织方式以及如何对其进行更改以控制生成会很有帮助。

说明

如果想要了解 MSBuild 如何独立于任何 SDK 的基本基础知识,本文是合适的。 本文未介绍使用 SDK 进行生成(例如使用 dotnet build 或将 Sdk 属性添加到根项目元素时)。 请参阅 .NET 项目 SDK

标准 .csproj 文件导入的生成逻辑支持比此示例更多的选项和更复杂的生成过程。

有关为C++项目创建项目文件的信息,请参阅 MSBuild (C++)

本教程介绍如何仅使用文本编辑器以增量方式创建基本项目文件。 演练采用以下步骤:

  1. 扩展 PATH 环境变量。

  2. 创建最小的应用程序源文件。

  3. 创建最小的 MSBuild 项目文件。

  4. 使用项目文件生成应用程序。

  5. 添加属性以控制生成。

  6. 通过更改属性值来控制构建。

  7. 将目标添加到生成。

  8. 通过指定目标来控制生成。

  9. 以增量方式生成。

本教程演示如何在命令提示符下生成项目并检查结果。 有关 MSBuild 以及如何在命令提示符下运行 MSBuild 的详细信息,请参阅 使用 MSBuild

若要完成本教程,必须安装 Visual Studio,因为它包括 MSBuild 和 C# 编译器,这是演练所必需的。

扩展路径

在使用 MSBuild 之前,必须扩展 PATH 环境变量以包含所有必需的工具。

如果在 Windows 上运行,可以使用 Visual Studio 开发人员命令提示符。 在 Windows 任务栏中的 Windows 搜索框中搜索它。 若要在普通命令提示符或脚本环境中设置环境,请在 Visual Studio 安装的 Common7/Tools 子文件夹中运行 VSDevCmd.bat

创建最小应用程序

本部分介绍如何使用文本编辑器创建最小的 C# 应用程序源文件。

  1. 在命令提示符下,浏览到要在其中创建应用程序的文件夹,例如,\My Documents\\Desktop\

  2. 创建名为 \HelloWorld\ 的子文件夹 并更改目录以进入其中。

  3. 在文本编辑器中,HelloWorld.cs 创建新文件,然后复制并粘贴以下代码:

    using System;
    
    class HelloWorld
    {
        static void Main()
        {
    #if DebugConfig
            Console.WriteLine("WE ARE IN THE DEBUG CONFIGURATION");
    #endif
    
            Console.WriteLine("Hello, world!");
        }
    }
    
  4. 通过在命令提示符处键入 csc helloworld.cs来生成应用程序。

  5. 通过在命令提示符处键入 helloworld 来测试应用程序。

    你好,世界! 消息应显示。

  6. 删除可执行文件。

创建最小 MSBuild 项目文件

有了最小的应用程序源文件后,可以创建一个最小的项目文件来生成应用程序。 此项目文件包含以下元素:

  • 必需的根 Project 节点。

  • 用于包含项元素的 ItemGroup 节点。

  • 引用应用程序源文件的项元素。

  • 一个 Target 节点,用于包含生成应用程序所需的任务。

  • 用于启动 C# 编译器以生成应用程序的 Task 元素。

创建最小的 MSBuild 项目文件

  1. 在文本编辑器中,HelloWorld.fromscratchproj 创建新文件,并输入以下代码:

    <Project>
      <ItemGroup>
        <Compile Include="helloworld.cs" />
      </ItemGroup>
    </Project>
    

    ItemGroup 包含项元素 Compile,并将一个源文件指定为项。

  2. Target 节点添加为 Project 节点的子元素。 将节点命名为 Build

    <Target Name="Build">
    </Target>
    
  3. 将此任务元素作为 Target 节点的子元素插入:

    <Csc Sources="@(Compile)"/>
    
  4. 保存此项目文件并将其命名 Helloworld.fromscratchproj

最小项目文件应类似于以下代码:

<Project>
  <ItemGroup>
    <Compile Include="helloworld.cs"/>
  </ItemGroup>
  <Target Name="Build">
    <Csc Sources="@(Compile)"/>
  </Target>
</Project>

Build 目标中的任务按顺序执行。 在这种情况下,C# 编译器 Csc 任务是唯一的任务。 它需要源文件列表进行编译,这由 Compile 项的值提供。 Compile 项仅引用一个源文件,Helloworld.cs

说明

在项元素中,可以使用星号通配符符号 (*) 来引用扩展名为 .cs 的所有文件,如下所示:

<Compile Include="*.cs" />

生成应用程序

现在,若要生成应用程序,请使用刚刚创建的项目文件。

  1. 在命令提示符下,键入 msbuild helloworld.fromscratchproj -t:Build

    此操作将调用 C# 编译器来创建 Helloworld 应用程序,从而生成 Helloworld 项目文件的生成目标。

  2. 通过键入 helloworld 来测试应用程序。

    应显示你好,世界! 消息。

说明

可以通过提高详细信息级别来查看有关生成的更多详细级别。 要将详细级别设置为“详细”,请在命令提示符下键入命令:

msbuild helloworld.fromscratchproj -t:Build -verbosity:detailed

添加生成属性

可以将生成属性添加到项目文件,以进一步控制生成。 现在添加以下属性:

  • 用于指定应用程序名称的 AssemblyName 属性。

  • 一个 OutputPath 属性,用于指定要包含应用程序的文件夹。

添加生成属性

  1. 删除现有的应用程序可执行文件(稍后,你将添加一个 Clean 目标来处理旧输出文件的删除)。

  2. 在项目文件中,在打开 Project 元素之后插入此 PropertyGroup 元素:

    <PropertyGroup>
      <AssemblyName>MSBuildSample</AssemblyName>
      <OutputPath>Bin\</OutputPath>
    </PropertyGroup>
    
  3. 将此任务添加到 Build 目标,置于 Csc 任务的前面:

    <MakeDir Directories="$(OutputPath)" Condition="!Exists('$(OutputPath)')" />
    

    MakeDir 任务创建由 OutputPath 属性命名的文件夹,前提是该名称不存在任何文件夹。

  4. 将此 OutputAssembly 属性添加到 Csc 任务:

    <Csc Sources="@(Compile)" OutputAssembly="$(OutputPath)$(AssemblyName).exe" />
    

    这指示 C# 编译器生成由 AssemblyName 属性命名的程序集,并将其放入由 OutputPath 属性命名的文件夹中。

  5. 保存更改。

项目文件现在应类似于以下代码:

<Project>
  <PropertyGroup>
    <AssemblyName>MSBuildSample</AssemblyName>
    <OutputPath>Bin\</OutputPath>
  </PropertyGroup>
  <ItemGroup>
    <Compile Include="helloworld.cs" />
  </ItemGroup>
  <Target Name="Build">
    <MakeDir Directories="$(OutputPath)" Condition="!Exists('$(OutputPath)')" />
    <Csc Sources="@(Compile)" OutputAssembly="$(OutputPath)$(AssemblyName).exe" />
  </Target>
</Project>

说明

我们建议在 OutputPath 元素中指定文件夹名称时,在名称末尾添加反斜杠(\)路径分隔符,而不是将其添加到 Csc 任务的 OutputAssembly 属性中。 因此,

<OutputPath>Bin\</OutputPath>

OutputAssembly="$(OutputPath)$(AssemblyName).exe" />

优于

<OutputPath>Bin</OutputPath>

OutputAssembly="$(OutputPath)\$(AssemblyName).exe" />

测试构建属性

现在,可以使用使用生成属性指定输出文件夹和应用程序名称的项目文件来生成应用程序。

  1. 在命令提示符下,键入 msbuild helloworld.fromscratchproj -t:Build

    这会创建 \Bin\ 文件夹,然后调用 C# 编译器来创建 MSBuildSample 应用程序,并将其置于 \Bin\ 文件夹中。

  2. 若要验证是否已创建 \Bin\ 文件夹,并且它是否包含 MSBuildSample 应用程序,请键入 dir Bin

  3. 通过键入 Bin\MSBuildSample 来运行可执行文件来测试应用程序。

    你好,世界! 消息应被显示。

添加生成目标

接下来,将另外两个目标添加到项目文件,如下所示:

  • 删除旧文件的“清理”目标。

  • 一个 Rebuild 目标,该目标使用 DependsOnTargets 特性,强制使 Clean 任务在 Build 任务之前运行。

拥有多个目标后,可以将生成目标设置为默认目标。

添加生成目标

  1. 在项目文件中,在生成目标之后添加这两个目标:

    <Target Name="Clean" >
      <Delete Files="$(OutputPath)$(AssemblyName).exe" />
    </Target>
    <Target Name="Rebuild" DependsOnTargets="Clean;Build" />
    

    Clean 目标调用删除任务以删除应用程序。 在 Clean 目标和 Build 目标均已运行之前,Rebuild 目标不会运行。 尽管“重新生成”目标没有任务,但它会导致“清理”目标在生成目标之前运行。

  2. 将此 DefaultTargets 特性添加到起始 Project 元素:

    <Project DefaultTargets="Build">
    

    这会将生成目标设置为默认目标。

项目文件现在应类似于以下代码:

<Project DefaultTargets="Build">
  <PropertyGroup>
    <AssemblyName>MSBuildSample</AssemblyName>
    <OutputPath>Bin\</OutputPath>
  </PropertyGroup>
  <ItemGroup>
    <Compile Include="helloworld.cs" />
  </ItemGroup>
  <Target Name="Build">
    <MakeDir Directories="$(OutputPath)" Condition="!Exists('$(OutputPath)')" />
    <Csc Sources="@(Compile)" OutputAssembly="$(OutputPath)$(AssemblyName).exe" />
  </Target>
  <Target Name="Clean" >
    <Delete Files="$(OutputPath)$(AssemblyName).exe" />
  </Target>
  <Target Name="Rebuild" DependsOnTargets="Clean;Build" />
</Project>

测试生成目标

可以练习新的生成目标来测试项目文件的这些功能:

  • 正在构建默认版本。

  • 在命令提示符处设置应用程序名称。

  • 在生成另一个应用程序之前删除应用程序。

  • 删除应用程序而不生成另一个应用程序。

测试生成目标

  1. 在命令提示符处,键入“msbuild helloworld.fromscratchproj -p:AssemblyName=Greetings”。

    由于未使用 -t 开关显式设置目标,因此 MSBuild 将运行默认生成目标。 -p 开关将替代 AssemblyName 属性,并为其提供新值,Greetings。 这会导致在 \Bin\ 文件夹中创建一个新的应用程序 Greetings.exe

  2. 若要验证 \Bin\ 文件夹是否包含 MSBuildSample 应用程序和新的 Greetings 应用程序,请键入 dir Bin

  3. 测试 Greetings 应用程序(例如,通过在 Windows 上键入 Bin\Greetings)。

    此时应会显示“Hello, world!”消息。

  4. 通过键入 msbuild helloworld.fromscratchproj -t:clean删除 MSBuildSample 应用程序。

    这会运行 Clean 任务以删除具有默认 AssemblyName 属性值的应用程序,MSBuildSample

  5. 通过键入 msbuild helloworld.fromscratchproj -t:clean -p:AssemblyName=Greetings删除 Greetings 应用程序。

    这将运行 Clean 任务,以删除具有指定 AssemblyName 属性值 Greetings 的应用程序。

  6. 若要验证 \Bin\ 文件夹现在为空,请键入 dir Bin

  7. 键入 msbuild。

    虽然未指定项目文件,但 MSBuild 会生成 helloworld.fromscratchproj 文件,因为当前文件夹中只有一个项目文件。 这会导致 MSBuildSample 应用程序在 \Bin\ 文件夹中创建。

    若要验证 \Bin\ 文件夹是否包含 MSBuildSample 应用程序,请键入 dir Bin

增量生成

仅当目标所依赖的源文件或目标文件已更改时,才能告知 MSBuild 生成目标。 MSBuild 使用文件的时间戳来确定该文件是否已更改。

以增量方式生成

  1. 在项目文件中,将这些属性添加到打开的生成目标:

    Inputs="@(Compile)" Outputs="$(OutputPath)$(AssemblyName).exe"
    

    这指定生成目标取决于在 Compile 项组中指定的输入文件,并且输出目标为应用程序文件。

    生成的生成目标应类似于以下代码:

    <Target Name="Build" Inputs="@(Compile)" Outputs="$(OutputPath)$(AssemblyName).exe">
      <MakeDir Directories="$(OutputPath)" Condition="!Exists('$(OutputPath)')" />
      <Csc Sources="@(Compile)" OutputAssembly="$(OutputPath)$(AssemblyName).exe" />
    </Target>
    
  2. 通过在命令提示符下键入 msbuild -v:d 来测试生成目标。

    请记住,helloworld.fromscratchproj 是默认项目文件,构建是默认目标。

    -v:d 开关是之前使用的 -verbosity:detailed 的缩写。

    如果已生成输出,则应显示以下行:

    正在跳过目标“Build”,因为所有输出文件相对于输入文件而言都是最新的。

    MSBuild 会跳过生成目标,因为自上次生成应用程序以来,源文件都没有发生更改。

C# 示例

下面的示例演示了一个项目文件,该文件编译 C# 应用程序并记录包含输出文件名的消息。

代码

<Project DefaultTargets = "Compile">

    <!-- Set the application name as a property -->
    <PropertyGroup>
        <appname>HelloWorldCS</appname>
    </PropertyGroup>

    <!-- Specify the inputs by type and file name -->
    <ItemGroup>
        <CSFile Include = "*.cs"/>
    </ItemGroup>

    <Target Name="Compile">
        <!-- Run the C# compilation using input files of type CSFile -->
        <CSC
            Sources = "@(CSFile)"
            OutputAssembly = "$(appname).exe">
            <!-- Set the OutputAssembly attribute of the CSC task
            to the name of the executable file that is created -->
            <Output
                TaskParameter = "OutputAssembly"
                ItemName = "EXEFile" />
        </CSC>
        <!-- Log the file name of the output file -->
        <Message Text="The output file is @(EXEFile)"/>
    </Target>
</Project>

Visual Basic 示例

下面的示例演示了一个项目文件,该文件编译 Visual Basic 应用程序并记录包含输出文件名的消息。

代码

<Project DefaultTargets = "Compile">

    <!-- Set the application name as a property -->
    <PropertyGroup>
        <appname>HelloWorldVB</appname>
    </PropertyGroup>

    <!-- Specify the inputs by type and file name -->
    <ItemGroup>
        <VBFile Include = "consolehwvb1.vb"/>
    </ItemGroup>

    <Target Name = "Compile">
        <!-- Run the Visual Basic compilation using input files of type VBFile -->
        <VBC
            Sources = "@(VBFile)"
            OutputAssembly= "$(appname).exe">
            <!-- Set the OutputAssembly attribute of the VBC task
            to the name of the executable file that is created -->
            <Output
                TaskParameter = "OutputAssembly"
                ItemName = "EXEFile" />
        </VBC>
        <!-- Log the file name of the output file -->
        <Message Text="The output file is @(EXEFile)"/>
    </Target>
</Project>

接下来会发生什么?

Visual Studio 可以自动执行本演练中显示的大部分工作。 若要了解如何使用 Visual Studio 创建、编辑、生成和测试 MSBuild 项目文件,请参阅 使用 MSBuild