MSBuild 入门

从 GitHub 上 Clone 下来 aspnetcore 源码后,要进行各种编译,出错~
为自己不了解错误背后的原因而感到难受,所以下定决心好好学习一下 MSBuild~

MSBuild 初体验

安装完 Visual Studio 2019 后,在下面两个目录下有 MSBuild.exe
C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin
C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\amd64
由于官方文档说一般情况下不使用 64版本,
所以我们将 C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin 添加到 环境变量 PATH

1
2
3
4
5
>msbuild
用于 .NET Framework 的 Microsoft (R) 生成引擎版本 16.7.0+b89cb5fde
版权所有(C) Microsoft Corporation。保留所有权利。

MSBUILD : error MSB1003: 请指定项目或解决方案文件。当前工作目录中未包含项目或解决方案文件。

创建一个 HelloWorld.xml 的内容为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="utf-8"?>
<!-- 根元素,表示一个项目 -->
<!-- DefaultTargets 用于定默认执行的目标 -->
<Project DefaultTargets="build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<!-- 属性都要包含在 PropertyGroup 元素内部 -->
<PropertyGroup>

<!-- 声明一个自定义"greeting"属性,其值为"hello world" -->
<greeting>hello world</greeting>

</PropertyGroup>

<!-- 目标 -->
<Target Name="build"> <!--DefaultTargets 指定的就是我-->
<!-- MSBuild 提供的一个内置任务,用于生成记录信息用 $(属性名) 来引用属性的值 -->
<Message Text="$(greeting)"></Message>

<!-- 使用 MSBuild 提供的保留属性,输出全路径 -->
<Message Text="$(MSBuildThisFileDirectory)$(MSBuildProjectFile)"></Message>
</Target>
</Project>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
D:\MSBuildDemo>msbuild HelloWorld.xml
用于 .NET Framework 的 Microsoft (R) 生成引擎版本 16.7.0+b89cb5fde
版权所有(C) Microsoft Corporation。保留所有权利。

生成启动时间为 yyyy/MM/DD HH:mm:ss。
节点 1 上的项目“D:\MSBuildDemo\HelloWorld.xml”(默认目标)。
build:
hello world
D:\MSBuildDemo\HelloWorld.xml
已完成生成项目“D:\MSBuildDemo\HelloWorld.xml”(默认目标)的操作。


已成功生成。
0 个警告
0 个错误

已用时间 00:00:00.04

创建最小的应用程序

D:\MSBuildDemo 下创建 Helloworld.cs 文件,内容为:

1
2
3
4
5
6
7
8
9
10
11
12
13
using System;

class HelloWorld
{
static void Main()
{
#if DebugConfig
Console.WriteLine("WE ARE IN THE DEBUG CONFIGURATION");
#endif

Console.WriteLine("Hello, world!");
}
}

在 Windows 任务栏的搜索框中进行搜索 Developer Command Prompt for VS 2019演示
以管理员身份运行 Developer Command Prompt for VS 2019,启动后应该是:

1
2
3
4
5
6
**********************************************************************
** Visual Studio 2019 Developer Command Prompt v16.7.0
** Copyright (c) 2020 Microsoft Corporation
**********************************************************************

C:\Windows\System32>

在命令提示符下,键入 csc D:\MSBuildDemo\HelloWorld.cs 来生成应用程序。

1
2
3
C:\Windows\System32>csc D:\MSBuildDemo\HelloWorld.cs
Microsoft(R) Visual C# 编译器 版本 3.7.0-6.20375.2 (34202cc2)
版权所有(C) Microsoft Corporation。保留所有权利。

编译成功后,在 C:\Windows\System32 会生成 HelloWorld.exe

在命令提示符下,键入 helloworld 测试应用程序。

1
2
C:\Windows\System32>helloworld
Hello, world!

将在 C:\Windows\System32 会生成的 HelloWorld.exe 删除

1
C:\Windows\System32>del helloworld.exe

创建最小的 MSBuild 项目文件

D:\MSBuildDemo 下创建 Helloworld.csproj 文件,内容为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- 必需的根 Project 节点。 -->
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<!-- 用于包含项元素的 ItemGroup 节点。 -->
<ItemGroup>
<!-- 引用应用程序源文件的项元素。 -->
<Compile Include="HelloWorld.cs" />
<!-- <Compile Include="*.cs" /> 可以使用通配符-->
</ItemGroup>

<!-- 一个 Target 节点,用于包含生成应用程序所需的任务。 -->
<Target Name="Build">
<!-- 此 Task 元素,作为 Target 节点的子元素:用于启动 Visual C# 编译器以生成应用程序。 -->
<Csc Sources="@(Compile)"/>
<!-- Build 目标中的任务按顺序执行。
在本例中,Visual C# 编译器 Csc 任务是唯一的任务。
它需要编译一系列源文件,这一系列文件由 Compile 项的值指定。
Compile 项只引用一个源文件,即 Helloworld.cs。 -->
</Target>

</Project>

运行 Developer Command Prompt for VS 2019,后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
C:\Windows\System32>msbuild D:\MSBuildDemo\HelloWorld.csproj
用于 .NET Framework 的 Microsoft (R) 生成引擎版本 16.7.0+b89cb5fde
版权所有(C) Microsoft Corporation。保留所有权利。

生成启动时间为 yyyy/MM/DD HH:mm:ss。
节点 1 上的项目“D:\MSBuildDemo\HelloWorld.csproj”(默认目标)。
Build:
C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\Roslyn\csc.exe /out:HelloWorld.exe
HelloWorld.cs
已完成生成项目“D:\MSBuildDemo\HelloWorld.csproj”(默认目标)的操作。


已成功生成。
0 个警告
0 个错误

已用时间 00:00:00.54

主要的是,HelloWorld.exe 是生成在与 HelloWorld.csproj 同一个目录。
新开一个 CMD 窗口,CD 到 D:\MSBuildDemo,运行 helloworld

1
2
D:\MSBuildDemo>helloworld
Hello, world!

如果使用 Developer Command Prompt for VS 2019 启动的窗口,
请注意 -t:Build 部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
D:\MSBuildDemo>msbuild helloworld.csproj -t:Build
用于 .NET Framework 的 Microsoft (R) 生成引擎版本 16.7.0+b89cb5fde
版权所有(C) Microsoft Corporation。保留所有权利。

生成启动时间为 yyyy/MM/DD HH:mm:ss。
项目“D:\MSBuildDemo\helloworld.csproj”在节点 1 上(Build 个目标)。
Build:
C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\Roslyn\csc.exe /out:HelloWorld.exe
HelloWorld.cs
已完成生成项目“D:\MSBuildDemo\helloworld.csproj”(Build 个目标)的操作。


已成功生成。
0 个警告
0 个错误

已用时间 00:00:00.52

添加生成属性

可以将生成属性添加到项目文件中,从而进一步控制生成。 现在添加以下属性:
一个 AssemblyName 属性,用于指定应用程序的名称。
一个 OutputPath 属性,用于指定要包含应用程序的文件夹。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

<!-- 必需的根 Project 节点。 -->
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!-- 定义程序集名称 -->
<AssemblyName>MSBuildSample</AssemblyName>
<!-- 定义编译后的输出目录 -->
<OutputPath>Bin\</OutputPath>
</PropertyGroup>

<!-- 用于包含项元素的 ItemGroup 节点。 -->
<ItemGroup>
<!-- 引用应用程序源文件的项元素。 -->
<Compile Include="HelloWorld.cs" />
<!-- <Compile Include="*.cs" /> 可以使用通配符-->
</ItemGroup>

<!-- 一个 Target 节点,用于包含生成应用程序所需的任务。 -->
<Target Name="Build">
<!-- Build 目标中的任务按顺序执行。现在有两个任务,一个创建文件夹,一个编译程序 -->
<!-- 将此任务添加到 Build 目标,置于 Csc 任务的前面:
MakeDir 任务将创建一个由 OutputPath 属性命名的文件夹,前提是当前不存在具有该名称的文件夹。 -->
<MakeDir Directories="$(OutputPath)" Condition="!Exists('$(OutputPath)')" />

<!-- 这将指示 Visual C# 编译器生成由 AssemblyName 属性命名的程序集,并将其放在由 OutputPath 属性命名的文件夹中。 -->
<Csc Sources="@(Compile)" OutputAssembly="$(OutputPath)$(AssemblyName).exe" />
</Target>

</Project>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
D:\MSBuildDemo>msbuild helloworld.csproj -t:Build
用于 .NET Framework 的 Microsoft (R) 生成引擎版本 16.7.0+b89cb5fde
版权所有(C) Microsoft Corporation。保留所有权利。

生成启动时间为 yyyy/MM/DD HH:mm:ss。
项目“D:\MSBuildDemo\helloworld.csproj”在节点 1 上(Build 个目标)。
Build:
正在创建目录“Bin\”。
C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\Roslyn\csc.exe /out:Bin\MSBuildSam
ple.exe HelloWorld.cs
已完成生成项目“D:\MSBuildDemo\helloworld.csproj”(Build 个目标)的操作。


已成功生成。
0 个警告
0 个错误

已用时间 00:00:00.52

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

OutputAssembly==”\$(OutputPath)\$(AssemblyName).exe” /> 不推荐有 \

优于
\<OutputPath>Bin\

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!-- 既然有多个 Target,就可以将 Build 目标设置为默认目标。 -->
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!-- 定义程序集名称 -->
<AssemblyName>MSBuildSample</AssemblyName>
<!-- 定义编译后的输出目录 -->
<OutputPath>Bin\</OutputPath>
</PropertyGroup>

<!-- 用于包含项元素的 ItemGroup 节点。 -->
<ItemGroup>
<!-- 引用应用程序源文件的项元素。 -->
<Compile Include="HelloWorld.cs" />
<!-- <Compile Include="*.cs" /> 可以使用通配符-->
</ItemGroup>

<!-- 一个 Target 节点,用于包含生成应用程序所需的任务。 -->
<Target Name="Build">
<!-- Build 目标中的任务按顺序执行。现在有两个任务,一个创建文件夹,一个编译程序 -->
<!-- 将此任务添加到 Build 目标,置于 Csc 任务的前面:
MakeDir 任务将创建一个由 OutputPath 属性命名的文件夹,前提是当前不存在具有该名称的文件夹。 -->
<MakeDir Directories="$(OutputPath)" Condition="!Exists('$(OutputPath)')" />

<!-- 这将指示 Visual C# 编译器生成由 AssemblyName 属性命名的程序集,并将其放在由 OutputPath 属性命名的文件夹中。 -->
<Csc Sources="@(Compile)" OutputAssembly="$(OutputPath)$(AssemblyName).exe" />
</Target>

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

<!-- 先执行 Clean 再执行 Build -->
<Target Name="Rebuild" DependsOnTargets="Clean;Build" />
</Project>

测试生成目标

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

    • 由于未使用 -t 开关显式设置目标,因此 MSBuild 运行默认 Build 目标。
    • -p 开关替代 AssemblyName 属性,并为它指定新值 Greetings。 这将导致在“\Bin\”文件夹中创建一个新应用程序 Greetings.exe。
  2. 要验证“\Bin\”文件夹是否同时包含 MSBuildSample 应用程序和新的 Greetings 应用程序,请键入“dir Bin”。

  3. 键入 Bin\Greetings 测试 Greetings 应用程序。

    • 显示的消息应为 Hello, world! 。
  4. 通过键入 msbuild helloworld.csproj -t:clean,删除 MSBuildSample 应用程序。

    • 这将运行 Clean 任务,以删除具有默认 AssemblyName 属性值 MSBuildSample 的应用程序。
  5. 通过键入 msbuild helloworld.csproj -t:clean -p:AssemblyName=Greetings,删除 Greetings 应用程序。

    • 这将运行 Clean 任务,以删除具有指定 AssemblyName 属性值 Greetings 的应用程序。
  6. 要验证“\Bin\”文件夹现在是否为空,请键入“dir Bin”。

  7. 键入 msbuild。

    • 尽管未指定项目文件,但 MSBuild 会生成 helloworld.csproj 文件,因为当前文件夹中只有一个项目文件。 这将导致在“\Bin\”文件夹中创建 MSBuildSample 应用程序。

小结一下:

  • 可以通过给 -t 传递 TargetName,来指定运行该 Target 下的任务。
  • 可以通过 -p: 来设置 PropertyGroup 节点中的参数值。

增量生成

可以指示 MSBuild 仅在目标所依赖的源文件或目标文件发生更改时才生成目标。 MSBuild 使用文件的时间戳来确定文件是否已更改。

Target Name="Build" 节点修改成如下:

1
2
3
4
<Target Name="Build" Inputs="@(Compile)" Outputs="$(OutputPath)$(AssemblyName).exe">
<MakeDir Directories="$(OutputPath)" Condition="!Exists('$(OutputPath)')" />
<Csc Sources="@(Compile)" OutputAssembly="$(OutputPath)$(AssemblyName).exe" />
</Target>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
D:\MSBuildDemo>msbuild -v:d
用于 .NET Framework 的 Microsoft (R) 生成引擎版本 16.7.0+b89cb5fde
版权所有(C) Microsoft Corporation。保留所有权利。

生成启动时间为 yyyy/MM/DD HH:mm:ss。
进程 = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\MSBuild.exe"
MSBuild 可执行文件路径 = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\MSBuild.exe"
命令行参数 = "msbuild -v:d"
当前目录 = "D:\MSBuildDemo"
MSBuild 版本 = "16.7.0+b89cb5fde"
节点 1 上的项目“D:\MSBuildDemo\Helloworld.csproj”(默认目标)。
正在使用工具版本“Current”进行生成。
项目“D:\MSBuildDemo\Helloworld.csproj”中的目标“Build”(入口点):
正在跳过目标“Build”,因为所有输出文件相对于输入文件而言都是最新的。
输入文件:HelloWorld.cs
输出文件:Bin\MSBuildSample.exe
已完成在项目“Helloworld.csproj”中生成目标“Build”的操作。
已完成生成项目“D:\MSBuildDemo\Helloworld.csproj”(默认目标)的操作。


已成功生成。
0 个警告
0 个错误

已用时间 00:00:00.04

难道是比较 HelloWorld.cs 文件最后的修改时间与 Bin\MSBuildSample.exe 文件最后的修改时间?

总结

最后按 https://docs.microsoft.com/zh-cn/visualstudio/msbuild/msbuild?view=vs-2019 小结一下我们了解到的内容~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!-- 在 MSBuild 中,元素和特性名称区分大小写。 但是,属性、项和元数据名称不区分大小写。 -->
<Project DefaultTargets="Build"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<!-- 属性 -->
<!-- 属性表示可用于配置生成的键/值对。 -->
<!-- 属性的声明方式是:创建一个与属性同名的元素,将其指定为 PropertyGroup 元素的子元素。 -->
<PropertyGroup>
<!-- 声明一个名位 AssemblyName 的属性,其值 MSBuildSample -->
<AssemblyName>MSBuildSample</AssemblyName>
<OutputPath>Bin\</OutputPath>
</PropertyGroup>

<!-- 项 -->
<!-- 项是生成系统的输入,通常表示文件。 将根据用户定义的项名称,将项编组到各种项类型中。 -->
<!-- 这些项类型可以用作任务的参数,任务使用各个项来执行生成过程的步骤。 -->
<ItemGroup>
<!-- 创建一个名为 Compile 的项类型 -->
<Compile Include="*.cs" />
<!-- 在整个项目文件中,可以使用语法 @(<ItemType>) 来引用项目类型。 -->
<!-- 例如,可以使用 @(Compile) 引用示例中的项类型。 -->
</ItemGroup>

<!-- 目标 -->
<!-- 目标按特定的顺序将任务组合到一起,并将项目文件的各个部分公开为生成过程的入口点。 -->
<Target Name="Build" Inputs="@(Compile)" Outputs="$(OutputPath)$(AssemblyName).exe">
<!-- 任务 -->
<!-- 任务是 MSBuild 项目用于执行生成操作的可执行代码单元。 -->
<!-- 例如,任务可能编译输入文件或运行外部工具 -->
<MakeDir Directories="$(OutputPath)" Condition="!Exists('$(OutputPath)')" />
<!-- 任务 -->
<Csc Sources="@(Compile)" OutputAssembly="$(OutputPath)$(AssemblyName).exe" />
</Target>

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

<!-- 先执行 Clean 再执行 Build -->
<Target Name="Rebuild" DependsOnTargets="Clean;Build" />
</Project>

心中的迷茫渐渐消去~
然后,当我们打开通过 Visual Studio 2019 创建一个 Web项目的时候,csproj 文件却是这样子的:

1
2
3
4
5
6
7
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>

</Project>

为何内容如此不同,感觉这一篇的内容都白学了,有没有~

参考资料

https://www.cnblogs.com/linianhui/archive/2012/08/30/2662648.html
https://docs.microsoft.com/zh-cn/visualstudio/msbuild/walkthrough-creating-an-msbuild-project-file-from-scratch?view=vs-2019
https://docs.microsoft.com/zh-cn/visualstudio/msbuild/msbuild?view=vs-2019

觉得文章对您有帮助,请我喝瓶肥宅快乐水可好 (๑•̀ㅂ•́)و✧
  • 本文作者: 阿彬~
  • 本文链接: https://iweixubin.github.io/posts/msbuild/getting-started/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 免责声明:本媒体部分图片,版权归原作者所有。因条件限制,无法找到来源和作者未进行标注。
         如果侵犯到您的权益,请与我联系删除