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 и т. Д.