这个帖子中的其他解决方案存在基于RelativeDir的../..问题,并且需要在每个源文件上手动设置。更不用说,它们会破坏/MP。任何指定确切.obj文件的解决方案都将导致每个.cpp文件(将其映射到特定的.obj文件)的不同/Fo传递给CL.exe,因此Visual Studio无法对它们进行批处理。如果没有批处理几个具有相同命令行(包括/Fo)的.cpp文件,则/MP无法工作。以下是一种新方法。这适用于至少vs2010到vs2015。将其添加到中的vcxproj中。
<!-- ================ UNDUPOBJ ================ -->
<!-- relevant topics -->
<!-- https://dev59.com/tW865IYBdhLWcg3wnP2a
<!-- https://dev59.com/R1rUa4cB1Zd3GeqPhS-U -->
<!-- https://dev59.com/-HXYa4cB1Zd3GeqP-tN4 -->
<!-- other maybe related info -->
<!-- https://dev59.com/5nRA5IYBdhLWcg3wvgob -->
<UsingTask TaskName="UNDUPOBJ_TASK" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<OutputDir ParameterType="System.String" Required="true" />
<ItemList ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
<OutputItemList ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true" />
</ParameterGroup>
<Task>
<Code><![CDATA[
//general outline: for each item (in ClCompile) assign it to a subdirectory of $(IntDir) by allocating subdirectories 0,1,2, etc., as needed to prevent duplicate filenames from clobbering each other
//this minimizes the number of batches that need to be run, since each subdirectory will necessarily be in a distinct batch due to /Fo specifying that output subdirectory
var assignmentMap = new Dictionary<string,int>();
HashSet<string> neededDirectories = new HashSet<string>();
foreach( var item in ItemList )
{
//solve bug e.g. Checkbox.cpp vs CheckBox.cpp
var filename = item.GetMetadata("Filename").ToUpperInvariant();
//assign reused filenames to increasing numbers
//assign previously unused filenames to 0
int assignment = 0;
if(assignmentMap.TryGetValue(filename, out assignment))
assignmentMap[filename] = ++assignment;
else
assignmentMap[filename] = 0;
var thisFileOutdir = Path.Combine(OutputDir,assignment.ToString()) + "/"; //take care it ends in / so /Fo knows it's a directory and not a filename
item.SetMetadata( "ObjectFileName", thisFileOutdir );
}
foreach(var needed in neededDirectories)
System.IO.Directory.CreateDirectory(needed);
OutputItemList = ItemList;
ItemList = new Microsoft.Build.Framework.ITaskItem[0];
]]></Code>
</Task>
</UsingTask>
<Target Name="UNDUPOBJ">
<!-- see stackoverflow topics for discussion on why we need to do some loopy copying stuff here -->
<ItemGroup>
<ClCompileCopy Include="@(ClCompile)"/>
<ClCompile Remove="@(ClCompile)"/>
</ItemGroup>
<UNDUPOBJ_TASK OutputDir="$(IntDir)" ItemList="@(ClCompileCopy)" OutputItemList="@(ClCompile)">
<Output ItemName="ClCompile" TaskParameter="OutputItemList"/>
</UNDUPOBJ_TASK>
</Target>
<!-- ================ UNDUPOBJ ================ -->
然后修改<project>,使其读取:
<Project InitialTargets="UNDUPOBJ" ...
结果将类似于 myproj/src/a/x.cpp 和 myproj/src/b/x.cpp 编译为 Debug/0/x.obj 和 Debug/1/x.obj。RelativeDirs 没有被使用,因此不是问题。
此外,在这种情况下,只会传递两个不同的 /Fo 给 CL.exe:Debug/0/ 和 Debug/1/。因此,最多只会向 CL.exe 发出两个批处理,从而使 /MP 更有效地工作。
其他方法可能基于 .cpp 子目录构建 .obj 子目录,或者使 .obj 文件名包含一些原始 .cpp 目录的提示,以便您可以轻松查看 .cpp->.obj 映射,但这些方法会导致更多的 /Fo,因此批处理较少。未来的工作可以转储映射文件以进行快速参考。
有关 /MP 和批处理的更多详细信息,请参见:
MSVC10 /MP builds not multicore across folders in a project
我已在各种工具链上的 vs2010 和 vs2015 中进行了生产测试。它似乎非常可靠,但总有可能与其他 msbuild 自定义或异国工具链产生不良交互。
从vs2015开始,如果您收到警告“warning MSB8027: Two or more files with the name of X.cpp will produce outputs to the same location”,则可以将以下内容添加到您的项目或msbuild文件中:
<PropertyGroup Label="Globals"><IgnoreWarnCompileDuplicatedFilename>true</IgnoreWarnCompileDuplicatedFilename></PropertyGroup>
查看更多内容请访问https://connect.microsoft.com/VisualStudio/feedback/details/797460/incorrect-warning-msb8027-reported-for-files-excluded-from-build和如何抑制特定的MSBuild警告
2019年9月更新:
我认为以上内容存在问题,其中ItemList可以任意排序,导致给定源文件重新分配到不同编号的目录中。因此,每当源文件列表发生变化时,对象的副本可能会出现重复。这也可能导致依赖跟踪有些混乱,因为输入输出映射可能会突然改变,使时间戳毫无意义。
我们可以通过对排序后的ItemList进行哈希并将其作为前缀添加到输出目录中来解决此问题,但这将创建额外的重建和大量的垃圾。
这可以通过放弃列出输入输出映射的文件,并使源文件到目标编号目录的映射成为一次性事件(直到项目被清理)来进一步完善。
我还需要深入调查此问题。