Visual Studio 2010&2008无法处理在不同文件夹中具有相同名称的源文件?

直接问题:如果我有两个具有相同名称(但在不同的目录中)的文件,似乎只有Visual Studio 2005可以处理这个透明的? VS 2008&2010需要一些调整? 除了我的命名规则,我做错了什么?

背景:

我正在开发C ++统计库…我有两个文件夹:

/ Univariate Normal.cpp Normal.h Beta.cpp Beta.h Adaptive.cpp Adaptive.h / Multivariate Normal.cpp Normal.h Beta.cpp Beta.h Adaptive.cpp Adaptive.h 

我需要支持交叉编译 – 我正在使用g ++ / make将这些相同的文件编译到Linux中的库中。 他们工作得很好。

我一直在使用Visual Studio 2005,但没有问题,但我需要升级到Visual Studio 2008或2010(目前stream口水nVidia的nsight工具)。 但是,如果我将文件添加到具有相同名称的项目(即使它们在不同的目录中),我遇到了麻烦。 我愿意改变我的命名约定,但我很好奇,如果别人遇到这个问题,并已发现任何据可查的解决scheme?

如果我从2005年的项目升级到2010年的项目,我感到进一步的惊讶,看起来VS 2010能够正确处理在不同目录中具有相同名称的两个源文件; 但是,如果我删除重复的文件之一,然后将其添加回项目,我受到以下警告:

Distributions \ Release \ Adaptive.obj:警告LNK4042:多次指定对象; 额外部分被忽略

现在我有指定为$(ProjectName)\ $(configuration)的中间目录 – 我需要我的对象文件在我的源代码树不同的位置。 所以我可以看到为什么要将对象文件复制到对方,但是当项目从2005年转换到2008年或2010年时,会添加一些条件编译:

 <ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)%(Filename)1.obj</ObjectFileName> <XMLDocumentationFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)%(Filename)1.xdc</XMLDocumentationFileName> 

可以从C / C ++ – > Output Files – >“Object File Name”和“XML Documentation File Name”中的Source file Properties页面访问这些文件。 但是,如果我直接添加文件(或删除并重新添加它们),VS不会抱怨,直到我试图编译,但也永远不会添加条件指令 – 所以为了事情正常工作,我必须我自己为每一个configuration添加条件指令。 我犯了一个错误/可怜的假设,或者我发现VS 2008/2010中有一个有效的错误?

所以@Hans Passant指出了正确的方向,谢谢! 您不必列出文件,一个文件夹就足够了。 然后,如果您查看VS 2010列表底部定义的macros,您将看到:

%(RelativeDir)/ Univariate /

发布的问题实际上是我正在处理的简化版本 – 单个项目中的几个级别的文件夹,并且存在一些名称冲突。 因此,我真的想要“修复”它…

如果在解决scheme资源pipe理器中右键单击该项目,请selectC / C ++ – >“输出文件”,然后在“对象文件名”框中键入以下内容:

$(IntDir)/%(RelativeDir)/

请注意,我也从下拉列表中select了(所有configuration,所有平台)。 这将编译目录层次结构中反映源代码树的每个文件。 如果不存在,VS2010将通过创build这些目录开始构build。 而且,对于那些讨厌在目录名称中使用空格的人来说,这个macros确实会删除所有的空格,所以在使用时不需要用双引号。

正是我想要的 – 与我的Makefiles在Ubuntu端工作的方式相同,同时仍然保持源码树清洁。

这在IDE中很容易修复。 点击文件夹中的第一个文件,Shift +点击最后一个文件,所有这些文件被选中。 右键单击“属性”,“C ++”,“输出文件”。 将对象文件名从$(IntDir)\更改为$(IntDir)\Univariate\ 。 您可以重复多元文件组,虽然这不是必要的。

你是对的,VS不能处理,永远不会。 根本问题是它为项目中的每个.cpp文件生成一个.obj文件,并且它们都放在同一个文件夹中。 所以你最终得到了多个编译为Adaptive.obj .cpp文件。

至less链接器现在会生成一个警告。 情况并非总是如此。

你应该能够通过确保文件使用不同的中间目录path来解决这个问题,但是对于应该是可能的东西来说,这是一种破解。

当然,您可以随时在Microsoft Connect上提交错误报告或function请求

在这个线程中的其他解决scheme遭受基于RelativeDir的../ ..问题,并不得不在每个源文件手动设置的东西。

更何况,他们破坏/ MP。 为%(ObjectFileName)指定一个确切的.obj的任何解决scheme将导致传递给CL.exe的每个.cpp文件(将其映射到特定.obj文件)的不同/ Fo,因此Visual Studio无法批处理。 没有使用相同的命令行(包括/ Fo)批处理几个.cpp文件,/ MP不能工作。

这是一个新的方法。 这至less可以在vs2010到vs2015之间运行。 将其添加到<project>中的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 eg 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更高效地工作。

其他方法是将.obj子目录放在.cpp子目录上,或者使.obj文件名包含一些原始.cpp目录的logging,这样就可以很容易地看到.cpp – >。obj映射,但是这些方法会导致更多/ Fo,因此配料量较less。 未来的工作可能会转储映射文件以供快速参考。

有关/ MP和批处理的更多详细信息,请参阅以下内容: MSVC10 / MP在项目中的文件夹之间不构build多核

我已经在vs2010和vs2015上testing了这个在生产中的相当长一段时间的各种工具链。 这似乎是防弹的,但总有一个机会可能会与其他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和如何禁止特定的MSBuild警告;

请注意,在我的实例(使用VS2010平台工具集的Visual Studio 2013)中,使用$(IntDir)\%(RelativeDir)无法正常工作,并且在构build多个configuration时忽略导致链接器错误的中间目录,每个configuration(即Debug和Release)的目标文件都放在同一个文件夹中。 如果在切换configuration时清理项目,则会消失。

错误示例:

MSVCRTD.lib(MSVCR100D.dll):错误LNK2005:_fclose已在LIBCMTD.lib(fclose.obj)中定义

解:

$(IntDir)\%(目录)

为了解决这个问题,我不得不使用$(IntDir)\%(Directory)将所有* .obj文件正确放置在中间目录下,并允许在不清理的情况下构build和链接多个configuration。 唯一的缺点是您的文件所在的整个(可能长的)文件夹层次结构将在Debug / Release / etc文件夹下完全重新创build。

也可以在Visual Studio中创build一个.cpp文件,然后将其重命名为.h。 虽然文件被重命名,但Visual Studio仍将其编译为cpp文件,因此会创build两个obj文件并显示链接器警告。

使用configuration属性> C / C ++> Ouptut文件> $(IntDir)\%(RelativeDir)\%(文件名)

这将复制debugging目录下的源文件结构,并将每个目录的目标文件存放在Debug目录下的同名文件夹中

%(RelativeDir)解决scheme仅适用于Visual Studo 2010。

对于Visual Studio 2008,您需要右键单击每个重复的.cpp文件名,然后select“属性”。 这可能并不明显,但实际上可以修改每个文件的“configuration属性 – > C / C ++ – > Ouptut文件”部分。

将一个子文件夹添加到每个重复的.cpp文件的“对象文件名”设置,注意.h文件不需要被修改,因为.cpp文件确定.obj文件输出。

例如,如果您的项目有两个冲突的文件:

internal \ example.cpp base \ example.cpp

您可以将每个对象的“对象文件名”设置为:

$(IntDir)\ internal \ $(IntDir)\ base \

您将需要为所有configuration发布/debugging等