Visual Studio 2010 и 2008 не могут обрабатывать исходные файлы с одинаковыми именами в разных папках?
Прямой вопрос: Если у меня есть два файла с одинаковым именем (но в разных каталогах), кажется, что только Visual Studio 2005 может обрабатывать это прозрачно? VS 2008 и 2010 требуют кучу настройки? Помимо моего соглашения об именах, я делаю что-то неправильно?
Фон:
Я разрабатываю статистические библиотеки С++... У меня есть две папки:
/ 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 способен правильно обрабатывать два исходных файла с одинаковым именем в разных каталогах; однако, если я удалю один из дубликатов файлов, а затем добавлю его обратно в проект, меня приветствует следующее предупреждение:
Распределения\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>
Они доступны на странице свойств исходного файла в C/С++ → Output Files → "Имя файла объекта" и "Имя файла документации XML". Но если я просто добавляю файл напрямую (или удаляю и повторно добавляю их), VS не жалуется, пока я не попытаюсь скомпилировать, но также никогда не добавляет условные директивы. Поэтому, чтобы все работало правильно, я должен добавьте условные директивы для каждой отдельной конфигурации. Я делаю ошибку/плохое предположение или я обнаружил действительную ошибку в VS 2008/2010?
Ответы
Ответ 1
Итак, @Hans Passant указал в правильном направлении, спасибо! Вам не нужно перечислять файл, достаточно папки. Затем, если вы посмотрите в определенных макросах в нижней части списка VS 2010, вы увидите:
% (RelativeDir)/Univariate/
Проблема, как было опубликовано, на самом деле была упрощенной версией того, над чем я работаю, - пару уровней папок в одном проекте, и есть несколько конфликтов имен. Следовательно, я действительно хотел как-то просто "исправить" его...
Если вы щелкните правой кнопкой мыши по проекту в проводнике решений, выберите C/С++ → "Output Files" и введите следующее в поле "Имя файла объекта":
$(IntDir)/% (RelativeDir)/
Обратите внимание, что я также выбрал (Все конфигурации, все платформы) из раскрывающихся списков. Это скомпилирует каждый файл в иерархии каталогов, который отображает исходное дерево. VS2010 начнет сборку, создав эти каталоги, если они не существуют. Кроме того, для тех, кто ненавидит пустое пространство в своих именах каталогов, этот макрос удаляет все пробелы, поэтому при использовании его нет необходимости играть с двойными кавычками.
Это точно, что я хотел - идентично тому, как мои Make файлы работают на стороне Ubuntu, сохраняя при этом исходное дерево чистым.
Ответ 2
Это легко исправить в среде IDE. Щелкните первый файл в папке, Shift + Нажмите последний файл, чтобы все они были выбраны. Щелкните правой кнопкой мыши, Properties, С++, Output Files. Измените имя файла объекта с $(IntDir)\
на, скажем, $(IntDir)\Univariate\
. Вы можете повторить для многомерной файловой группы, хотя это не является строго необходимым.
Ответ 3
Вы правы, VS не справляется с этим, и никогда не мог. Коренная проблема заключается в том, что она генерирует файл .obj
для каждого файла .cpp
в проекте, и все они помещаются в одну и ту же папку. Таким образом, вы в конечном итоге получаете несколько файлов .cpp
для компиляции в Adaptive.obj
в вашем случае.
По крайней мере, компоновщик генерирует предупреждение для него сейчас. Это не всегда так.
Вы должны иметь возможность обойти это, гарантируя, что файлы используют разные пути промежуточной каталогов, но это немного взломать что-то, что должно быть возможно.
Конечно, вы всегда можете найти отчет об ошибке или запрос функции на нем Microsoft Connect
Ответ 4
Другие решения в этом потоке страдают от проблем, связанных с RelativeDir../.. и необходимости устанавливать вещи вручную в каждом исходном файле.
Не говоря уже, они крушат/МП. Любое решение, которое задает точный .obj для% (ObjectFileName), приведет к другому /Fo для каждого .cpp файла (для сопоставления его конкретному файлу .obj), переданного CL.exe, и, таким образом, Visual Studio не может их загружать, Без пакетного ввода нескольких файлов .cpp с идентичными командами (в том числе /Fo )/MP не может работать.
Вот новый подход. Это работает по vs2010 до vs2015 по крайней мере. Добавьте это в свой vcxproj в <project>
<!-- ================ UNDUPOBJ ================ -->
<!-- relevant topics -->
<!-- https://stackoverflow.com/questions/3729515/visual-studio-2010-2008-cant-handle-source-files-with-identical-names-in-diff/26935613 -->
<!-- /questions/6248/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 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 arent используется, и поэтому не проблема.
Кроме того, в этом случае в CL.exe будет только два разных /Fo: Debug/0/и Debug/1/. Следовательно, для CL.exe выдается не более двух партий, что позволяет /MP работать более эффективно.
Другие подходы будут базировать подкаталоги .obj в подкаталогах .cpp или сделать имя файла .obj, содержащее некоторый список исходного каталога .cpp, чтобы вы могли легко видеть .cpp → . obj mapping, но те в результате получается больше /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
Ответ 5
Обратите внимание, что в моем экземпляре (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.
Ответ 6
Также может быть, что вы создаете файл .cpp в visual studio и позже переименовываете его в .h. Хотя файл переименовывается, визуальная студия все еще компилирует его как файл cpp, и поэтому создаются два файла obj и показано предупреждение о компоновщике.
Ответ 7
используйте Свойства конфигурации > C/С++ > Файлы Ouptut > $(IntDir)\%(RelativeDir)\%(имя файла)
это приведет к дублированию структуры исходного файла в каталоге отладки и внесению объектного файла для каждого каталога в папку с тем же именем в каталоге Отладка
Ответ 8
Решение% (RelativeDir) работает только для Visual Studo 2010.
Для Visual Studio 2008 вам нужно щелкнуть правой кнопкой мыши по каждому дубликатному имени файла .cpp и выбрать "Свойства". Это может быть не очевидно, но вы можете изменить раздел "Свойства конфигурации → C/С++ → Ouptut Files" для каждого отдельного файла.
Добавьте подпапку в настройку "Object File Name" каждого из повторяющихся .cpp файлов, обратите внимание, что .h файлы не нужно изменять, так как файл .cpp определяет выход .obj файла.
Например, если ваш проект имеет два конфликтующих файла:
внутренняя\example.cpp
база \example.cpp
Вы должны установить для "Имя файла объекта" для каждого:
$(IntDir)\внутренний \
$ (IntDir)\база\
Вам нужно будет сделать это для всех конфигураций Release/Debug и т.д.