Visual Studio 如何知道我的项目是最新的以便它可以跳过 运行 MSBuild?
How does Visual Studio know my project is up to date so it can skip running MSBuild?
我的 C++ 项目中包含一个自定义 MSBuild 目标,它在 $(OutDir) 文件夹中为给定项目类型的每个项目生成一个数据文件。我将项目类型与 属性 页面架构连接起来,因此您可以 select 在解决方案资源管理器中的文件上使用它,并且我的目标声明输入和输出,以便增量构建工作。我还将我的目标添加到 $(BuildDependsOn) 属性 中,因此它会在构建目标 Visual Studio 调用期间自动评估。
除了一件事,一切似乎都正常:如果我删除 $(OutDir) 中的一个输出数据文件,然后构建 Visual Studio 什么都不做,并说我的项目是最新的。如果我删除项目生成的 exe 文件或触摸其中一个 MSBuild 脚本的修改时间 Visual Studio 重新评估 targts 并发现输出文件丢失,导致它使用我的目标重新构建。
从 MSBuild 诊断日志来看,Visual Studio 似乎在内部维护一些输出文件和输入文件的列表,它检查这些文件以避免评估 MSBuild 脚本。如何将我的输出文件添加到此列表?
MsBuild/VS 确实有一种机制来确定输入文件的最新信息,它围绕着一个可执行文件 tracker.exe,它扫描 .tlog 文件以找出什么是项目的输出文件是。可能还有更多内容,如果您在 Internet 上四处看看,您可能会获得更多相关信息。
但问题是你真的不需要了解它的每一个细节:你可以在检查内置 CustomBuildStep 的方式时找到一个简单的用法示例有效并将其应用于您的案例。我将简要解释一下我是如何做到这一点的,因为我认为它可能对您在处理此类 msbuild 问题时也很有用。
如果你添加
<ItemDefinitionGroup>
<CustomBuildStep>
<Command>echo foo > $(OutDir)\foo.txt</Command>
<Outputs>$(OutDir)\foo.txt</Outputs>
</CustomBuildStep>
</ItemDefinitionGroup>
手动或通过项目的 属性 页面进行 自定义构建步骤 您将看到行为正是您所需要的:如果 foo.txt 是deleted 一个构建将开始,而一个构建被标记为最新,如果它不是(好吧,当其余的输出也是最新的)。
因此关键是做 CustomBuildStep 在幕后做的事情,弄清楚这只是使用您选择的工具搜索所有出现的 CustomBuildStep 的问题C:\Program Files (x86)\MSBuild\Microsoft.Cpp\v4.0\V120 下的文件(调整使用的 platform/VS 版本的路径)。
这将我们带到 Microsoft.CppCommon.Targets,其中名为 CustomBuildStep
的目标(请注意,它与上面 ItemDefinitionGroup 中的条目同名)调用实际的 CustomBuildStep 命令。它还有这个特别有趣的地方:
<!-- Appended tlog to track custom build events -->
<WriteLinesToFile Encoding="Unicode"
File="$(TLogLocation)$(ProjectName).write.1u.tlog"
Lines="@(CustomBuildStep->'^%(Identity)');@(CustomBuildStep->MetaData('Outputs')->FullPath()->Distinct())"/>
因此,这会将输出路径写入跟踪器使用的目录中的 .tlog 文件,并使其按需要工作。有关格式的更多信息,另请参阅 here。
tl;dr 使用 WriteLinesToFile 将目标输出的完整路径附加到 $(TLogLocation)$(ProjectName).write.1u.tlog 等文件中。我说 喜欢 因为 write.tlog、write.u.tlog 等也有效。
您可以通过在注册表项中启用快速更新检查器的详细信息来找出项目被重建的原因:
New-ItemProperty `
-Name U2DCheckVerbosity `
-PropertyType DWORD -Value 1 `
-Path HKCU:\Software\Microsoft\VisualStudio.0\General -Force
您应该能够在构建日志中看到类似
的消息
Project 'Caliburn.Micro.Silverlight.Extensions' is not up to date. Project item 'C:\dev\projects\Caliburn.Micro.Silverlight.Extensions\NavigationBootstrapperSample.cs.pp' has 'Copy to Output Directory' attribute set to 'Copy always'.
Visual Studio 使用称为 Visual Studio 通用项目系统 (CPS) 的东西 (https://github.com/Microsoft/VSProjectSystem) (VS 2017)
管理项目,包括构建过程。
在 CPS 中,任何实现 IBuildUpToDateCheckProvider 接口的东西都可以使用
作为项目的 'UpToDateChecker'。
'UpToDateChecker' 在调用 MsBuild 之前被调用。它的主要目的是判断是否调用MsBuild来构建项目,或者将项目标记为'Up To Date'并一直跳过msbuild。
这 'UpToDateChecker' 正是打印到诊断构建输出中的内容:
1>------ Up-To-Date check: Project: "ProjectName", Configuration:
Debug x86 ------ Project is not up-to-date: build input 'header.h' was
modified after build output 'a.out'. Input time: 12/27/2018 4:43:08
PM, Output time: 1/1/0001 2:00:00 AM
至于 C++ 项目,对于 VS 2017,其默认 'UpToDateChecker' 是 VCProjectBuildUpToDateCheck
(Microsoft.VisualStudio.Project.VisualC.VCProjectEngine.dll)。
作为启动器,它会在 tlogs 目录(通常类似于 Debug\x86\.tlog)中查找这些文件:
- .lastbuildstate
- 构建失败
- all '.read..tlog' - 输入文件,在诊断构建输出中标记为 'build input'
- all '.write..tlog' - 输出文件,在诊断构建输出中标记为 'build output'
其实检查的比较多,但是失败最多的是检查这4种
此处的原始问题与 C++ 项目有关,但对于在搜索有关现代(SDK 样式)C#/VB/F# 项目的信息时发现此问题的任何人,您可以自定义 Visual Studio 的快速如本文档所述进行最新检查:
https://github.com/dotnet/project-system/blob/master/docs/up-to-date-check.md
简而言之,您将输入和输出指定为项目:
UpToDateCheckInput
— 描述 MSBuild 在其他情况下不会知道的输入文件
UpToDateCheckBuilt
— 描述 MSBuild 否则不知道的输出文件
通过此设置提高诊断日志记录级别以进行最新检查非常有帮助:
为旧式项目(即非 SDK 式项目,在 .NET Framework 时代很常见)启用日志记录:
- 为您正在使用的 Visual Studio 的特定版本打开“开发人员命令提示符”。
- 输入指令:
vsregedit set "%cd%" HKCU General U2DCheckVerbosity dword 1
- 应显示消息
Set value for U2DCheckVerbosity
。
运行 使用 0
而不是 1
的相同命令来禁用此日志记录。
更多信息位于:https://github.com/dotnet/project-system/blob/main/docs/up-to-date-check.md#net-framework-projects
我的 C++ 项目中包含一个自定义 MSBuild 目标,它在 $(OutDir) 文件夹中为给定项目类型的每个项目生成一个数据文件。我将项目类型与 属性 页面架构连接起来,因此您可以 select 在解决方案资源管理器中的文件上使用它,并且我的目标声明输入和输出,以便增量构建工作。我还将我的目标添加到 $(BuildDependsOn) 属性 中,因此它会在构建目标 Visual Studio 调用期间自动评估。
除了一件事,一切似乎都正常:如果我删除 $(OutDir) 中的一个输出数据文件,然后构建 Visual Studio 什么都不做,并说我的项目是最新的。如果我删除项目生成的 exe 文件或触摸其中一个 MSBuild 脚本的修改时间 Visual Studio 重新评估 targts 并发现输出文件丢失,导致它使用我的目标重新构建。
从 MSBuild 诊断日志来看,Visual Studio 似乎在内部维护一些输出文件和输入文件的列表,它检查这些文件以避免评估 MSBuild 脚本。如何将我的输出文件添加到此列表?
MsBuild/VS 确实有一种机制来确定输入文件的最新信息,它围绕着一个可执行文件 tracker.exe,它扫描 .tlog 文件以找出什么是项目的输出文件是。可能还有更多内容,如果您在 Internet 上四处看看,您可能会获得更多相关信息。
但问题是你真的不需要了解它的每一个细节:你可以在检查内置 CustomBuildStep 的方式时找到一个简单的用法示例有效并将其应用于您的案例。我将简要解释一下我是如何做到这一点的,因为我认为它可能对您在处理此类 msbuild 问题时也很有用。
如果你添加
<ItemDefinitionGroup>
<CustomBuildStep>
<Command>echo foo > $(OutDir)\foo.txt</Command>
<Outputs>$(OutDir)\foo.txt</Outputs>
</CustomBuildStep>
</ItemDefinitionGroup>
手动或通过项目的 属性 页面进行 自定义构建步骤 您将看到行为正是您所需要的:如果 foo.txt 是deleted 一个构建将开始,而一个构建被标记为最新,如果它不是(好吧,当其余的输出也是最新的)。
因此关键是做 CustomBuildStep 在幕后做的事情,弄清楚这只是使用您选择的工具搜索所有出现的 CustomBuildStep 的问题C:\Program Files (x86)\MSBuild\Microsoft.Cpp\v4.0\V120 下的文件(调整使用的 platform/VS 版本的路径)。
这将我们带到 Microsoft.CppCommon.Targets,其中名为 CustomBuildStep
的目标(请注意,它与上面 ItemDefinitionGroup 中的条目同名)调用实际的 CustomBuildStep 命令。它还有这个特别有趣的地方:
<!-- Appended tlog to track custom build events -->
<WriteLinesToFile Encoding="Unicode"
File="$(TLogLocation)$(ProjectName).write.1u.tlog"
Lines="@(CustomBuildStep->'^%(Identity)');@(CustomBuildStep->MetaData('Outputs')->FullPath()->Distinct())"/>
因此,这会将输出路径写入跟踪器使用的目录中的 .tlog 文件,并使其按需要工作。有关格式的更多信息,另请参阅 here。
tl;dr 使用 WriteLinesToFile 将目标输出的完整路径附加到 $(TLogLocation)$(ProjectName).write.1u.tlog 等文件中。我说 喜欢 因为 write.tlog、write.u.tlog 等也有效。
您可以通过在注册表项中启用快速更新检查器的详细信息来找出项目被重建的原因:
New-ItemProperty `
-Name U2DCheckVerbosity `
-PropertyType DWORD -Value 1 `
-Path HKCU:\Software\Microsoft\VisualStudio.0\General -Force
您应该能够在构建日志中看到类似
的消息Project 'Caliburn.Micro.Silverlight.Extensions' is not up to date. Project item 'C:\dev\projects\Caliburn.Micro.Silverlight.Extensions\NavigationBootstrapperSample.cs.pp' has 'Copy to Output Directory' attribute set to 'Copy always'.
Visual Studio 使用称为 Visual Studio 通用项目系统 (CPS) 的东西 (https://github.com/Microsoft/VSProjectSystem) (VS 2017) 管理项目,包括构建过程。
在 CPS 中,任何实现 IBuildUpToDateCheckProvider 接口的东西都可以使用 作为项目的 'UpToDateChecker'。 'UpToDateChecker' 在调用 MsBuild 之前被调用。它的主要目的是判断是否调用MsBuild来构建项目,或者将项目标记为'Up To Date'并一直跳过msbuild。
这 'UpToDateChecker' 正是打印到诊断构建输出中的内容:
1>------ Up-To-Date check: Project: "ProjectName", Configuration: Debug x86 ------ Project is not up-to-date: build input 'header.h' was modified after build output 'a.out'. Input time: 12/27/2018 4:43:08 PM, Output time: 1/1/0001 2:00:00 AM
至于 C++ 项目,对于 VS 2017,其默认 'UpToDateChecker' 是 VCProjectBuildUpToDateCheck (Microsoft.VisualStudio.Project.VisualC.VCProjectEngine.dll)。 作为启动器,它会在 tlogs 目录(通常类似于 Debug\x86\.tlog)中查找这些文件:
- .lastbuildstate
- 构建失败
- all '.read..tlog' - 输入文件,在诊断构建输出中标记为 'build input'
- all '.write..tlog' - 输出文件,在诊断构建输出中标记为 'build output'
其实检查的比较多,但是失败最多的是检查这4种
此处的原始问题与 C++ 项目有关,但对于在搜索有关现代(SDK 样式)C#/VB/F# 项目的信息时发现此问题的任何人,您可以自定义 Visual Studio 的快速如本文档所述进行最新检查:
https://github.com/dotnet/project-system/blob/master/docs/up-to-date-check.md
简而言之,您将输入和输出指定为项目:
UpToDateCheckInput
— 描述 MSBuild 在其他情况下不会知道的输入文件UpToDateCheckBuilt
— 描述 MSBuild 否则不知道的输出文件
通过此设置提高诊断日志记录级别以进行最新检查非常有帮助:
为旧式项目(即非 SDK 式项目,在 .NET Framework 时代很常见)启用日志记录:
- 为您正在使用的 Visual Studio 的特定版本打开“开发人员命令提示符”。
- 输入指令:
vsregedit set "%cd%" HKCU General U2DCheckVerbosity dword 1
- 应显示消息
Set value for U2DCheckVerbosity
。
运行 使用 0
而不是 1
的相同命令来禁用此日志记录。
更多信息位于:https://github.com/dotnet/project-system/blob/main/docs/up-to-date-check.md#net-framework-projects