天呐!你知道MSBuild都干了些什么

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了天呐!你知道MSBuild都干了些什么相关的知识,希望对你有一定的参考价值。

一个典型的.NET5.0项目文件是这样的,看着非常简洁:

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

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
  </ItemGroup>

</Project>

但是,当我们执行“生成”时,却可以看到输出了大量日志,完全不知道这些目标都是哪来的? 

我们知道,生成操作实际是由MSBuild执行的。

那么,MSBuild到底干了什么?

查看日志

虽然,只要你在选项里设置日志级别为“诊断”,项目生成时会输出非常详细的日志记录: 

但是,这样生成的文本日志量太大了,要找出需要的信息难如登天。

这时,我们可以使用“MSBuild结构化日志查看器”,以可视化的方式查看日志。

安装

查看器的安装依赖Chocolatey。

首先,以管理员身份打开命令提示符,运行下列命令安装Chocolatey:

Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))

然后,运行下列命令安装日志查看器:

choco install msbuild-structured-log-viewer

生成日志

打开MSBuild Structured Log Viewer,选择“Open Project/Solution”,打开我们新建的Web API示例项目WebApplication1.sln,点击“Build”按钮生成日志:

运行完成后,你应该可以看到如下内容:

点击项目名称左边的箭头展开后,可以看到MSBuild准备执行的所有目标,每个目标中包含多个任务:

灰色的表示跳过的目标,展开后可以看到跳过的原因。

下面,我们以bin\\Debug\\net5.0\\Swashbuckle.AspNetCore.Swagger.dll文件怎么输出的为例,演练如何分析日志。

分析日志

Copy任务

在左侧Search Log窗口上方,输入bin\\Debug\\net5.0\\Swashbuckle.AspNetCore.Swagger.dll作为条件:

可以看到文件是由_CopyFilesMarkedCopyLocal目标中的Copy任务生成的,选中后在中间Log窗口双击任务名,会在右侧窗口显示任务详情,原来任务来源于MSBuild\\Current\\Bin\\Microsoft.Common.CurrentVersion.targets文件。

Copy任务作用是将源文件ReferenceCopyLocalPaths复制到目标文件$(OutDir)%(DestinationSubDirectory)%(Filename)%(Extension)

那么源文件和目标文件的值,又是从哪来的呢?

OutDir属性

我们可以轻易地查找到$(OutDir)的值等于bin\\Debug\\net5.0,却没看到bin\\Debug\\net5.0这个值是由谁赋给它的:

通过左侧的Find In Files窗口,原来它来自于MSBuild\\Current\\Bin\\amd64\\Microsoft.Common.CurrentVersion.targets文件,从OutputPath赋值:

metaproj文件

OutputPath的值来源于同一个文件,等于$(BaseOutputPath)$(Configuration)\\

BaseOutputPath也来源于这个文件。但奇怪的是,Configuration却来源于一个叫做WebApplication1.sln.metaproj的文件:

项目目录下并没有这个文件啊?!

随后,我们在日志中找到这样一条消息:

已生成元项目“D:\\Codes\\WebApplication1\\WebApplication1.sln.metaproj”。

而且,在WebApplication1.sln.metaproj中,我们还可以找到Rebuild目标:

而Rebuild又依赖于其他目标:

你还记得生成日志时,带的/t:Rebuild参数吗?

现在清楚了,MSBuild启动时首先生成.metaproj文件,然后根据文件中的元数据,按照依赖关系执行目标。

DestinationSubDirectory属性

但是,%(DestinationSubDirectory)在日志里并没有找到任何赋值的位置。

试着继续探索原始文件来源,最终定位到了ResolvePackageAssets目标下的ResolvePackageAssets任务:

具体参数值对应任务的输出参数RuntimeAssemblies

<Output TaskParameter="RuntimeAssemblies" ItemName="RuntimeCopyLocalItems" />

查看dotnet/sdk/Tasks/Microsoft.NET.Build.Tasks/ResolvePackageAssets.cs的源码,RuntimeAssemblies的类型是ITaskItem[]

ITaskItem定义如下:

public interface ITaskItem
{
    string ItemSpec { get; set; }
    int MetadataCount { get; }
    ICollection MetadataNames { get; }

    IDictionary CloneCustomMetadata();
    void CopyMetadataTo(ITaskItem destinationItem);
    string GetMetadata(string metadataName);
    void RemoveMetadata(string metadataName);
    void SetMetadata(string metadataName, string metadataValue);
}

接着,我们找到这样一段代码:

if (!string.IsNullOrEmpty(destinationSubDirectory))
{
    WriteMetadata(MetadataKeys.DestinationSubDirectory, destinationSubDirectory);
}

DestinationSubDirectory原来是Metadata啊!

结论

根据上面的分析,可以梳理出bin\\Debug\\net5.0\\Swashbuckle.AspNetCore.Swagger.dll文件如何输出的整个流程:

  • MSBuild启动,根据项目文件生成.metaproj文件

  • MSBuild根据/t参数, 从.metaproj文件中读取目标

  • 根据目标的依赖关系,按顺序执行其他目标

  • 其中,ResolvePackageAssets目标下的ResolvePackageAssets任务获取项目所有依赖包的Metadata

  • 再由_CopyFilesMarkedCopyLocal目标中的Copy任务遍历依赖包,根据Metadata复制文件到指定目录下的指定文件名

现在,你可以跟同事show一下:我知道MSBuild干了什么!

如果你觉得这篇文章对你有所启发,请关注我的个人公众号”My IO“,记住我!

以上是关于天呐!你知道MSBuild都干了些什么的主要内容,如果未能解决你的问题,请参考以下文章

编译器优化代码都干了些什么不为人知的事情?

看看C# 6.0中那些语法糖都干了些什么(上篇)

程序媛字节裸辞后,都干了些什么....

一周读书哲学家,你们都干了些什么?

《哲学家们都干了些什么》——林欣浩

修正版|Spark任务提交后都干了些什么?