此线程中的其他解决方案存在基于 RelativeDir 的 ../.. 问题,并且必须在每个源文件上手动设置。
更不用说,它们破坏了 /MP。任何为 %(ObjectFileName) 指定精确 .obj 的解决方案都会导致传递给 CL.exe 的每个 .cpp 文件(以将其映射到特定的 .obj 文件)的 /Fo 不同,因此 Visual Studio 无法对它们进行批处理.如果不使用相同的命令行(包括 /Fo)批处理多个 .cpp 文件,/MP 将无法工作。
这是一种新方法。这至少适用于 vs2010 到 vs2015。将此添加到
中的 vcxproj
<!-- ================ UNDUPOBJ ================ -->
<!-- relevant topics -->
<!-- https://stackoverflow.com/questions/3729515/visual-studio-2010-2008-cant-handle-source-files-with-identical-names-in-diff/26935613 -->
<!-- https://stackoverflow.com/questions/7033855/msvc10-mp-builds-not-multicore-across-folders-in-a-project -->
<!-- https://stackoverflow.com/questions/18304911/how-can-one-modify-an-itemdefinitiongroup-from-an-msbuild-target -->
<!-- other maybe related info -->
<!-- https://stackoverflow.com/questions/841913/modify-msbuild-itemgroup-metadata -->
<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 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 更有效地工作。
其他方法是将 .obj 子目录基于 .cpp 子目录,或者使 .obj 文件名包含原始 .cpp 目录的一些纪念品,以便您可以轻松查看 .cpp->.obj 映射,但那些导致更多 /Fo 并因此减少批处理。未来的工作可能会转储一个映射文件以供快速参考。
有关 /MP 和批处理的更多详细信息,请参阅此处:MSVC10 /MP builds not multicore across folders in a project
我已经在生产环境中在各种工具链上的 vs2010 和 vs2015 上对此进行了一段时间的测试。它似乎是防弹的,但它总有可能与其他 msbuild 自定义或外来工具链交互不良。
从 vs2015 开始,如果您收到警告“警告 MSB8027:两个或多个名为 X.cpp 的文件将产生输出到同一位置”,那么您可以将其添加到您的项目或 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 和How to suppress specific MSBuild warning 上查看更多信息
2019 年 9 月更新:
我认为上面存在一个问题,即 ItemList 可以任意排序,从而导致给定源文件重新分配到不同编号的目录。因此,每当源文件列表更改时,可能会出现对象的重复副本。这也可能导致依赖跟踪变得有点混乱,因为输入->输出映射可能会发生惊人的变化,因此时间戳没有意义。
我们可以通过散列已排序的 ItemList 并将其添加到输出目录的前缀来解决此问题,但这会产生额外的重建和大量垃圾。
这可以通过删除一个列出输入->输出映射的文件来改进,并将源文件到目标编号目录的映射作为一次性事件(直到项目被清理)。
我还没有对此进行更深入的调查。