programing

Visual Studio 2010 및 2008은 다른 폴더에서 동일한 이름을 가진 소스 파일을 처리 할 수 ​​없습니까?

coolbiz 2021. 1. 14. 22:58
반응형

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으로 업그레이드해야합니다 (현재 nVidia의 nsight 도구에 흠뻑 젖음). 그러나 이름이 같은 프로젝트에 파일을 추가하면 문제가 발생합니다 (다른 디렉토리에 있더라도). 이름 지정 규칙을 변경하고 싶지만 다른 사람들이이 문제를 겪고 문서화 된 솔루션을 찾았는지 궁금합니다 .

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 ++-> 출력 파일-> "개체 파일 이름"및 "XML 문서 파일 이름"의 소스 파일 속성 페이지에서 액세스 할 수 있습니다. 그러나 단순히 파일을 직접 추가 (또는 제거하고 다시 추가)하면 VS가 컴파일을 시도 할 때까지 불평하지 않고 조건부 지시문도 추가하지 않습니다. 따라서 제대로 작동하려면 다음을 수행해야합니다. 모든 단일 구성에 대해 조건부 지시문을 직접 추가하십시오. 실수하거나 잘못된 가정을하고 있거나 VS 2008/2010에서 유효한 버그를 발견 했습니까?


그래서 @Hans Passant는 올바른 방향을 가리 켰습니다. 파일을 나열 할 필요가 없으며 폴더로 충분합니다. 그런 다음 VS 2010 목록 하단에 정의 된 매크로를 보면 다음과 같은 내용이 표시됩니다.

% (RelativeDir) / 일 변량 /

게시 된대로 문제는 실제로 제가 작업중인 작업의 단순화 된 버전이었습니다. 단일 프로젝트에서 몇 가지 수준의 폴더와 몇 가지 이름 충돌이 있습니다. 따라서 나는 정말로 그것을 "고치기"를 원했다 ...

솔루션 탐색기에서 프로젝트를 마우스 오른쪽 단추로 클릭하면 C / C ++-> "출력 파일"을 선택하고 "개체 파일 이름"상자에 다음을 입력합니다.

$ (IntDir) / % (RelativeDir) /

또한 드롭 다운에서 (모든 구성, 모든 플랫폼)을 선택했습니다. 이것은 소스 트리를 미러링하는 디렉토리 계층 구조의 모든 파일을 컴파일합니다. VS2010은 존재하지 않는 경우 이러한 디렉터리를 만들어 빌드를 시작합니다. 또한 디렉토리 이름에서 공백을 싫어하는 사람들을 위해이 매크로는 모든 공백을 제거하므로 사용할 때 큰 따옴표를 사용할 필요가 없습니다.

이것은 정확히 내가 원했던 것입니다. 소스 트리를 깨끗하게 유지하면서 내 Makefile이 Ubuntu 측에서 작동하는 방식과 동일합니다.


이것은 IDE에서 쉽게 수정할 수 있습니다. 폴더의 첫 번째 파일을 클릭하고 Shift + 마지막 파일을 클릭하여 모두 선택합니다. 마우스 오른쪽 버튼을 클릭하고 속성, C ++, 출력 파일을 클릭합니다. 개체 파일 이름을에서 $(IntDir)\변경하십시오 $(IntDir)\Univariate\. 꼭 필요한 것은 아니지만 다 변수 파일 그룹에 대해 반복 할 수 있습니다.


당신 말이 맞아요, VS는 그것을 처리 할 수없고, 절대 할 수 없습니다. 근본 문제는 프로젝트의 .obj.cpp파일에 대해 파일을 생성 하고 모두 동일한 폴더에 배치 된다는 것 입니다. 따라서 예를 들어 여러 .cpp파일이 컴파일되는 Adaptive.obj경우가 있습니다.

적어도 링커는 지금 경고를 생성합니다. 항상 그런 것은 아닙니다.

파일이 서로 다른 중간 디렉토리 경로를 사용하도록하여이 문제를 해결할 수 있어야하지만 가능 해야하는 문제에 대한 약간의 해킹 입니다.

물론 언제든지 Microsoft Connect 에서 버그 보고서 나 기능 요청을 제출할 수 있습니다.


이 스레드의 다른 솔루션은 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>를 수정하여 다음과 같이 읽습니다.

<Project InitialTargets="UNDUPOBJ" ...

결과는 Debug / 0 / x.obj 및 Debug / 1 / x.obj로 컴파일하는 myproj / src / a / x.cpp 및 myproj / src / b / x.cpp와 같습니다. RelativeDirs는 사용되지 않으므로 문제가되지 않습니다.

Additionally, in this case, there will be only two different /Fo passed to CL.exe: Debug/0/ and Debug/1/. Consequently, no more than two batches will get issued to CL.exe, allowing the /MP to work more efficiently.

Other approaches would be basing the .obj subdirectories on the .cpp subdirectories, or making the .obj filename contain some memento of the original .cpp directory so that you can readily see a .cpp->.obj mapping, but those result in more /Fo and therefore less batching. Future work could dump a mapping file for quick reference, perhaps.

See this for more details on /MP and batching : MSVC10 /MP builds not multicore across folders in a project

I've tested this in production for quite a while on vs2010 and vs2015 on a variety of toolchains. It seems bulletproof, but there's always a chance it may interact badly with other msbuild customizations or exotic toolchains.

Starting in vs2015, if you get a warning "warning MSB8027: Two or more files with the name of X.cpp will produce outputs to the same location" then you can add this to your project or msbuild files:

<PropertyGroup Label="Globals"><IgnoreWarnCompileDuplicatedFilename>true</IgnoreWarnCompileDuplicatedFilename></PropertyGroup>

See more at https://connect.microsoft.com/VisualStudio/feedback/details/797460/incorrect-warning-msb8027-reported-for-files-excluded-from-build and How to suppress specific MSBuild warning


UPDATE SEP 2019:

I believe there is a problem with the above where the ItemList can be arbitrarily ordered resulting in reassignments of a given source file to varying numbered directories. Thus whenever the source file list changes, duplicate copies of the object may appear. This may also cause dependency tracking to get a bit messed up, since the input->output mapping can get changed surprisingly so that the timestamps make no sense.

We could fix this by hashing a sorted ItemList and prefixing that to the output directory, but this will create extra rebuilds and a lot of litter.

This could be refined by dropping a file listing input->output mappings, and having the mapping of a source file to destination numbered-directory be a one-time only event (until the project is cleaned).

I have yet to investigate this more deeply.


Note that in my instance (Visual Studio 2013 with VS2010 platform toolset), using $(IntDir)\%(RelativeDir) doesn't work correctly, and it ignores the intermediate directory which results in linker errors when building multiple configurations because all of the object files for every configuration (ie, Debug and Release) are placed in the same folder. These go away if you clean the project when switching configurations.

Error Example:

MSVCRTD.lib(MSVCR100D.dll) : error LNK2005: _fclose already defined in LIBCMTD.lib(fclose.obj)

Solution:

$(IntDir)\%(Directory)

To get around this, I had to use $(IntDir)\%(Directory) which correctly placed all of the *.obj files under the intermediate directory and allowed building and linking multiple configurations without cleaning. The only downside is the entire (potentially long) folder hierarchy that your files are in will be completely re-created under the Debug/Release/etc folder.


use Configuration Properties > C/C++ > Ouptut Files> $(IntDir)\%(RelativeDir)\%(Filename)

this will duplicate the source file structure under the debug directory and deposit the object file for each directory in a folder of the same name under the Debug directory


It also can be that you create a .cpp file in visual studio and later rename it to .h. Although the file is renamed visual studio still compiles it as a cpp file and therefore two obj files are created and linker warning shown.


The %(RelativeDir) solution only works for Visual Studo 2010.

For Visual Studio 2008 you need to right click on each duplicate .cpp filename and choose "Properties". It may not be obvious but you can actually modify the "Configuration Properties -> C/C++ -> Ouptut Files" section for each individual file.

Add a sub folder to the "Object File Name" setting of each of the duplicate .cpp files, note that .h files do not need to be modified as the .cpp file determines the .obj file output.

For example, if your project has two conflicting files:

internal\example.cpp base\example.cpp

You would set the "Object File Name" for each to:

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

You will need to do this for all configurations Release/Debug etc.

ReferenceURL : https://stackoverflow.com/questions/3729515/visual-studio-2010-2008-cant-handle-source-files-with-identical-names-in-diff

반응형