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 (в настоящее время слюни над инструментом nsid от nVidia). Однако у меня возникают проблемы, если я добавляю файлы в проект с тем же именем (даже если они находятся в другом каталоге). Я готов изменить свое соглашение об именах, но мне любопытно, сталкивались ли другие с этой проблемой и нашли ли какие-либо хорошо документированные решения??

Кроме того, меня поражает тот факт, что, если я обновлюсь с проектов 2005 до проектов 2010, создается впечатление, что VS 2010 способен правильно обрабатывать два исходных файла с одинаковыми именами в разных каталогах; однако, если я удаляю один из дубликатов файлов и затем добавляю его обратно в проект, меня приветствует следующее предупреждение:

Распределения \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?

8 ответов

Решение

Так @Hans Passant указал в правильном направлении, спасибо! Вам не нужно перечислять файл, достаточно папки. Затем, если вы посмотрите в определенных макросах внизу списка VS 2010, вы увидите:

% (RelativeDir) / одномерный /

Проблема, как было написано, на самом деле была упрощенной версией того, над чем я работаю - пара уровней папок в одном проекте и пара конфликтов имен. Следовательно, я действительно хотел как-то просто "починить" это...

Если вы щелкнете правой кнопкой мыши по проекту в обозревателе решений, выберите C/C++ -> "Выходные файлы" и введите в поле "Имя файла объекта" следующее:

$ (IntDir) /% (RelativeDir) /

Обратите внимание, что я также выбрал (Все конфигурации, Все платформы) из выпадающих. Это скомпилирует каждый файл в иерархии каталогов, которая отражает исходное дерево. VS2010 начнет сборку с создания этих каталогов, если они не существуют. Кроме того, для тех, кто ненавидит пробелы в именах каталогов, этот макрос удаляет все пробелы, поэтому при его использовании нет необходимости возиться с двойными кавычками.

Это именно то , что я хотел - идентично тому, как мои Makefiles работают на стороне Ubuntu, при этом сохраняя чистоту исходного дерева.

Это легко исправить в IDE. Нажмите первый файл в папке, Shift+ щелкните последний файл, чтобы выбрать все из них. Щелкните правой кнопкой мыши, Свойства, C++, Выходные файлы. Изменить имя файла объекта с $(IntDir)\ сказать, $(IntDir)\Univariate\, Вы можете повторить для группы файлов Multivariate, хотя это не является строго обязательным.

Вы правы, В.С. не может справиться с этим, и никогда не мог. Корень проблемы в том, что он генерирует .obj файл для каждого .cpp файл в проекте, и все они находятся в одной папке. Таким образом, вы в конечном итоге с несколькими .cpp файлы, компилируемые в Adaptive.obj в вашем случае, например.

По крайней мере, компоновщик генерирует предупреждение для него сейчас. Это не всегда так.

Вы должны быть в состоянии обойти это, убедившись, что файлы используют разные пути к промежуточным каталогам, но это что-то вроде взлома того, что должно быть возможно.

Конечно, вы всегда можете подать отчет об ошибке или запрос функции на него в Microsoft Connect

Обратите внимание, что в моем случае (Visual Studio 2013 с набором инструментов платформы VS2010) использование $(IntDir)\%(RelativeDir) работает некорректно и игнорирует промежуточный каталог, что приводит к ошибкам компоновщика при создании нескольких конфигураций, поскольку все объектные файлы для каждой конфигурации (например, Debug и Release) помещаются в одну папку. Они исчезнут, если вы очистите проект при переключении конфигураций.

Пример ошибки:

MSVCRTD.lib (MSVCR100D.dll): ошибка LNK2005: _fclose уже определено в LIBCMTD.lib(fclose.obj)

Решение:

$(IntDir)\%(Directory)

Чтобы обойти это, мне пришлось использовать $(IntDir)\%(Directory), который правильно помещал все файлы *.obj в промежуточный каталог и позволял создавать и связывать несколько конфигураций без очистки. Единственным недостатком является то, что вся (потенциально длинная) иерархия папок, в которой находятся ваши файлы, будет полностью воссоздана в папке Debug/Release/etc.

Другие решения в этой теме страдают от проблем на основе RelativeDir../.. и необходимости вручную настраивать каждый исходный файл.

Не говоря уже о том, что они разрушены / МП. Любое решение, в котором указан точный.obj для%(ObjectFileName), приводит к тому, что для каждого файла.cpp (для сопоставления его с конкретным файлом.obj) передается отдельный файл /Fo, поэтому Visual Studio не может их пакетировать, Без пакетирования нескольких файлов.cpp с одинаковыми командными строками (включая /Fo) /MP не может работать.

Вот новый подход. Это работает с vs2010 до vs2015 по крайней мере. Добавьте это к вашему vcxproj в

<!-- ================ UNDUPOBJ ================ -->
<!-- relevant topics -->
<!-- https://stackru.com/questions/3729515/visual-studio-2010-2008-cant-handle-source-files-with-identical-names-in-diff/26935613 -->
<!-- https://stackru.com/questions/7033855/msvc10-mp-builds-not-multicore-across-folders-in-a-project -->
<!-- https://stackru.com/questions/18304911/how-can-one-modify-an-itemdefinitiongroup-from-an-msbuild-target -->
<!-- other maybe related info -->
<!-- https://stackru.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 stackru 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 не работают и поэтому не являются проблемой.

Кроме того, в этом случае в CL.exe будут переданы только два разных /Fo: 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>

Подробнее см. По адресу https://connect.microsoft.com/VisualStudio/feedback/details/797460/incorrect-warning-msb8027-reported-for-files-excluded-from-build и Как подавить определенные предупреждения MSBuild

Используйте Свойства конфигурации> C/C++ > Выходные файлы> $(IntDir)\%(RelativeDir)\%(Имя файла)

это дублирует структуру исходного файла в каталоге отладки и помещает объектный файл для каждого каталога в одноименную папку в каталоге отладки.

Также возможно, что вы создаете файл.cpp в visual studio, а затем переименовываете его в.h. Хотя файл переименован, Visual Studio все равно компилирует его как файл cpp, и поэтому создаются два файла obj и отображается предупреждение компоновщика.

Решение%(RelativeDir) работает только для Visual Studo 2010.

Для Visual Studio 2008 вам нужно щелкнуть правой кнопкой мыши по каждому дублирующему файлу.cpp и выбрать "Свойства". Это может быть неочевидно, но вы можете изменить раздел "Свойства конфигурации -> C/C++ -> Выходные файлы" для каждого отдельного файла.

Добавьте подпапку к параметру "Имя файла объекта" каждого из дубликатов файлов.cpp, обратите внимание, что файлы.h не нужно изменять, поскольку файл.cpp определяет вывод файла.obj.

Например, если ваш проект имеет два конфликтующих файла:

внутренняя \example.cpp base\example.cpp

Вы должны установить "Имя файла объекта" для каждого:

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

Вам нужно будет сделать это для всех конфигураций Release/Debug и т. Д.