解决Visual Studio非编译/嵌入资源文件变更时增量构建失效问题
在VS2017的.NET Core项目里,默认情况下MSBuild只会自动跟踪<Compile>和<EmbeddedResource>这类标准项的变更来触发增量构建。你的自定义<MyItem>文件变更后没触发增量构建,核心原因是MSBuild不知道要把这些自定义文件作为构建目标的输入,也没有明确的输出用来对比时间戳。下面是具体的解决步骤:
1. 给自定义目标添加输入输出标记
MSBuild的增量构建依赖于「输入文件时间戳」和「输出文件时间戳」的对比,只要输入比输出新,就会执行目标。你需要给你的MyCompileTarget目标明确指定Inputs和Outputs属性:
示例1:有明确输出文件的场景
如果你的自定义目标会生成一个物理文件(比如处理后的file.dat),可以这样修改.csproj:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp2.0</TargetFramework> </PropertyGroup> <ItemGroup> <MyItem Include="file.dat" /> </ItemGroup> <PropertyGroup> <PrepareResourcesDependsOn> $(PrepareResourcesDependsOn); MyCompileTarget </PrepareResourcesDependsOn> </PropertyGroup> <!-- 自定义目标,明确输入输出 --> <Target Name="MyCompileTarget" Inputs="@(MyItem)" Outputs="$(OutputPath)file.processed.dat"> <!-- 这里替换成你的实际处理逻辑,比如文件转换、复制等 --> <Copy SourceFiles="@(MyItem)" DestinationFiles="$(OutputPath)file.processed.dat" /> </Target> </Project>
Inputs="@(MyItem)":告诉MSBuild,这个目标的输入是你定义的MyItem组里的所有文件;Outputs="$(OutputPath)file.processed.dat":指定目标执行后生成的输出文件,MSBuild会自动对比输入和输出的时间戳,只有输入更新时才重新执行目标。
示例2:无明确输出文件的场景
如果你的自定义目标没有生成物理文件(比如只是执行一些逻辑操作),可以创建一个时间戳标记文件来充当输出:
<Project Sdk="Microsoft.NET.Sdk"> <!-- 其他配置不变 --> <Target Name="MyCompileTarget" Inputs="@(MyItem)" Outputs="$(IntermediateOutputPath)MyTarget.timestamp"> <!-- 你的自定义处理逻辑 --> <Message Text="Processing file.dat..." Importance="High" /> <!-- 生成标记文件,用来跟踪构建状态 --> <Touch Files="$(IntermediateOutputPath)MyTarget.timestamp" AlwaysCreate="true" /> </Target> </Project>
$(IntermediateOutputPath)是项目的中间文件目录(一般是obj\Debug\netcoreapp2.0\),这个标记文件会在目标执行时更新时间戳,MSBuild通过它来判断是否需要重新执行目标。
2. 验证增量构建是否生效
修改file.dat后,执行Build操作,你会看到自定义目标只有在文件变更时才会执行。如果想更清楚地看到MSBuild的判断过程,可以用命令行执行:
msbuild YourProject.csproj -v:d
在详细日志里,你能找到关于增量构建的判断信息,确认MyItem是否被正确识别为输入。
3. 调整目标执行时机(可选)
如果你需要自定义目标在特定阶段执行,可以替换PrepareResourcesDependsOn,改用BeforeTargets或AfterTargets属性,比如:
<Target Name="MyCompileTarget" Inputs="@(MyItem)" Outputs="$(OutputPath)file.processed.dat" BeforeTargets="CoreCompile"> <!-- 逻辑 --> </Target>
这样目标会在核心编译步骤之前执行,根据你的实际需求调整即可。
内容的提问来源于stack exchange,提问作者Fabio Strocco

