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

M. *_*its 55 c++ projects-and-solutions naming-conventions visual-studio-2010 visual-studio-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
Run Code Online (Sandbox Code Playgroud)

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

我一直在使用Visual Studio 2005而没有问题,但我需要升级到Visual Studio 2008或2010(目前在nVidia的nsight工具上流口水).但是,如果我将文件添加到具有相同名称的项目中(即使它们位于不同的目录中),我会遇到问题.我愿意改变我的命名惯例,但是我很好奇其他人是否遇到过这个问题并且找到了任何记录良好的解决方案?

我通过,如果我从2005年项目升级至2010年的项目,似乎VS 2010的事实进一步猛然一惊能够正确地处理在不同目录中的两个名称相同的源文件; 但是,如果我删除其中一个重复文件,然后将其添加回项目,我会收到以下警告:

Distributions\Release\Adaptive.obj:警告LNK4042:指定了多次的对象; 临时演员被忽略了

现在我将中间目录指定为$(ProjectName)\ $(配置) - 我需要将我的目标文件放在与源代码树不同的位置.所以我可以看到为什么它将对象文件复制在一起,但是当项目从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>
Run Code Online (Sandbox Code Playgroud)

可以从C/C++ - >输出文件 - >"对​​象文件名"和"XML文档文件名"中的源文件属性页访问它们.但是,如果我只是直接添加文件(或删除并重新添加它们),VS在我尝试编译之前不会抱怨,但也从不添加条件指令 - 所以为了使事情正常工作,我必须为每个配置自己添加条件指令.我是犯了错误/不好的假设还是我在VS 2008/2010中发现了一个有效的错误?

M. *_*its 64

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

%(RelativeDir)/单变量/

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

如果右键单击解决方案资源管理器中的项目,请选择"C/C++" - >"输出文件",然后在"对象文件名"框中键入以下内容:

$(IntDir)/%(RelativeDir)/

请注意,我还从下拉菜单中选择了(所有配置,所有平台).这将编译镜像源树的目录层次结构中的每个文件.VS2010将通过创建这些目录(如果它们不存在)来开始构建.此外,对于那些讨厌目录名称中的空格的人来说,这个宏会删除所有空格,因此在使用它时不需要使用双引号.

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

  • 我的项目比源更深一些,所以RelativeDir的值是`../../ src /`,但我发现使用`$(IntDir)/ fake/dir /%(RelativeDir)/`工作否定这两个部分 (2认同)
  • 最初我无法在 VS 宏中找到 `%(RelativeDir)`,但是这篇文章帮助了我。虽然我的对象最终出现在解决方案的顶级根构建中。所以我改用了以下内容:`$(IntDir)\$(ProjectName)\$(ConfigurationName)\%(RelativeDir)`。 (2认同)

Han*_*ant 12

这很容易在IDE中修复.单击文件夹中的第一个文件,Shift +单击最后一个文件,以便选中所有文件.右键单击,属性,C++,输出文件.从更改目标文件名$(IntDir)\,比方说,$(IntDir)\Univariate\.您可以重复多变量文件组,尽管这不是绝对必要的.


jal*_*alf 8

你是对的,VS无法处理,也永远无法做到.根本问题是它.obj.cpp项目中的每个文件生成一个文件,并且它们都放在同一个文件夹中.因此,您最终.cppAdaptive.obj在您的情况下编译多个文件.

至少链接器现在会为它生成警告.情况并非总是如此.

您应该能够通过确保文件使用不同的中间目录路径来解决这个问题,但这是一个应该可能的事情.

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

  • 我想可能已经存在[这个错误](http://connect.microsoft.com/VisualStudio/feedback/details/361228/visual-c-compilers-only-one-file-of-two-with-the -一样的名字).建议的解决方法之一是手动更改一个(或两个)目标文件的生成的目标文件名.您应该能够立即覆盖所有配置,因此实现起来不应太繁琐. (2认同)

zer*_*mus 7

此线程中的其他解决方案受到基于RelativeDir的../ ..问题的困扰,并且必须在每个源文件上手动设置.

更不用说,他们是残骸/ MP.任何为%(ObjectFileName)指定精确.obj的解决方案都会导致传递给CL.exe的每个.cpp文件(将其映射到特定.obj文件)的不同/ Fo,因此Visual Studio无法批处理它们.如果没有使用相同的命令行(包括/ Fo)批量处理几个.cpp文件,/ MP就无法工作.

这是一种新方法.这至少可以在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 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 ================ -->
Run Code Online (Sandbox Code Playgroud)

然后修改<project>,使其显示为:

<Project InitialTargets="UNDUPOBJ" ...
Run Code Online (Sandbox Code Playgroud)

结果将类似于myproj/src/a/x.cpp和myproj/src/b/x.cpp编译为Debug/0/x.obj和Debug/1/x.obj.没有使用RelativeDir所以没有问题.

此外,在这种情况下,只有两个不同的/ Fo传递给CL.exe:Debug/0 /和Debug/1 /.因此,将向CL.exe发出不超过两个批次,从而允许/ MP更有效地工作.

其他方法是将.obj子目录基于.cpp子目录,或使.obj文件名包含原始.cpp目录的一些纪念品,以便您可以很容易地看到.cpp - > .obj映射,但这些会导致更多/ Fo因此配料较少.未来的工作可能会转储一个映射文件,以便快速参考.

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

我已经在vs2010和vs2015上对各种工具链进行了相当长的测试.它似乎是防弹的,但它总是有可能与其他msbuild自定义或异国情调的工具链进行严重交互.

从vs2015开始,如果你收到警告"警告MSB8027:两个或多个名为X.cpp的文件将产生输出到同一位置",那么你可以将它添加到你的项目或msbuild文件中:

<PropertyGroup Label="Globals"><IgnoreWarnCompileDuplicatedFilename>true</IgnoreWarnCompileDuplicatedFilename></PropertyGroup>
Run Code Online (Sandbox Code Playgroud)

有关详细信息,请访问https://connect.microsoft.com/VisualStudio/feedback/details/797460/incorrect-warning-msb8027-reported-for-files-excluded-from-build以及如何禁止特定的MSBuild警告


小智 5

使用配置属性> C / C ++>输出文件> $(IntDir)\%(RelativeDir)\%(文件名)

这将重复调试目录下的源文件结构,并存入每个目录对象文件具有相同名称的文件夹中的下调试目录


syp*_*lex 5

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

错误示例:

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

解:

$(IntDir)\%(目录)

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