web-dev-qa-db-fra.com

Visual Studio 2010 & 2008 ne peuvent pas gérer les fichiers source avec des noms identiques dans des dossiers différents?

Question directe: Si j'ai deux fichiers portant le même nom (mais dans des répertoires différents), il apparaît que seul Visual Studio 2005 peut gérer cela de manière transparente? VS 2008 & 2010 nécessitent quelques ajustements? Mis à part ma convention d'appellation, est-ce que je fais quelque chose de mal?

Contexte:

Je développe des bibliothèques statistiques C++ ... J'ai deux dossiers:

/ 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

Je dois prendre en charge la compilation croisée. J'utilise g ++/make pour compiler ces mêmes fichiers dans une bibliothèque sous Linux. Ils fonctionnent très bien.

J'utilisais Visual Studio 2005 sans problème, mais je dois effectuer une mise à niveau vers Visual Studio 2008 ou 2010 (bave actuellement sur l'outil nsight de nVidia). Toutefois, je ne parviens pas à ajouter des fichiers à un projet portant le même nom (même s'ils se trouvent dans un répertoire différent). Je suis disposé à changer ma convention d'appellation, mais je suis curieux de savoir si d'autres personnes ont rencontré ce problème et ont trouvé des solutions bien documentées ??

Je suis encore plus ébahi par le fait que si je passe de projets de 2005 à projets de 2010, il apparaît que VS 2010 est capable de gérer correctement deux fichiers sources portant le même nom dans des répertoires différents; Cependant, si je supprime l'un des fichiers en double, puis le rajoute au projet, l'avertissement suivant me salue:

Distributions\Version\Adaptive.obj: avertissement LNK4042: objet spécifié plusieurs fois; extras ignorés

Maintenant, le répertoire intermédiaire est spécifié sous la forme $ (NomProjet)\$ (Configuration) - Je dois avoir les fichiers objet dans un emplacement différent de celui de mon arborescence source. Je peux donc voir pourquoi on copie les fichiers objet les uns sur les autres, mais lorsque les projets sont convertis de 2005 à 2008 ou 2010, un ensemble de compilations conditionnelles est ajouté:

<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)%(Filename)1.obj</ObjectFileName>
<XMLDocumentationFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)%(Filename)1.xdc</XMLDocumentationFileName>

Ceux-ci sont accessibles à partir de la page Propriétés du fichier source dans C/C++ -> Fichiers de sortie -> "Nom de fichier d'objet" & "Nom de fichier de documentation XML". Mais si j'ajoute simplement le fichier directement (ou le supprime et le rajoute), VS ne se plaint pas tant que je n'ai pas essayé de compiler, mais n'ajoute jamais non plus les directives conditionnelles - Pour que les choses fonctionnent correctement, je dois ajoutez moi-même les directives conditionnelles pour chaque configuration. Est-ce que je fais une erreur/mauvaise hypothèse ou ai-je découvert un bogue valide dans VS 2008/2010?

51
M. Tibbits

@Hans Passant a donc indiqué la bonne direction, merci !! Vous n'êtes pas obligé de lister le fichier, un dossier suffit. Ensuite, si vous regardez dans les macros définies au bas de la liste de VS 2010, vous verrez:

% (RelativeDir)/Univarié /

Le problème, tel que publié, était en fait une version simplifiée de ce sur quoi je travaille - quelques niveaux de dossiers dans un même projet et quelques conflits de noms. Par conséquent, je voulais vraiment pouvoir le "réparer" ...

Si vous cliquez avec le bouton droit sur le projet dans l'explorateur de solutions, choisissez C/C++ -> "Fichiers de sortie" et tapez ce qui suit dans la zone "Nom de fichier de l'objet":

$ (IntDir) /% (RelativeDir) /

Notez que j'ai également sélectionné (Toutes les configurations, Toutes les plates-formes) dans les listes déroulantes. Cela compilera chaque fichier d'une hiérarchie de répertoires reflétant l'arbre source. VS2010 commencera la construction en créant ces répertoires s'ils n'existent pas. En outre, pour les personnes qui détestent les espaces dans leurs noms de répertoire, cette macro supprime tous les espaces. Il est donc inutile de jouer avec les guillemets doubles.

Ceci est exactement ce que je voulais - identique à la façon dont mes Makefiles fonctionnent du côté Ubuntu, tout en maintenant la propreté de l’arbre source.

57
M. Tibbits

C'est facile à corriger dans l'EDI. Cliquez sur le premier fichier du dossier, Maj + clic sur le dernier fichier pour les sélectionner tous. Cliquez avec le bouton droit de la souris sur Propriétés, C++, Fichiers de sortie. Changez le nom du fichier objet de $(IntDir)\ en, par exemple, $(IntDir)\Univariate\. Vous pouvez répéter l'opération pour le groupe de fichiers multivarié bien que cela ne soit pas strictement nécessaire.

12
Hans Passant

Vous avez raison, VS ne peut pas gérer cela et ne le pourrait jamais. Le problème fondamental est qu'il génère un fichier .obj pour chaque fichier .cpp du projet et qu'ils sont tous placés dans le même dossier. Ainsi, vous vous retrouvez avec plusieurs fichiers .cpp compilés en Adaptive.obj dans votre cas, par exemple.

Au moins l'éditeur de liens génère un avertissement pour cela maintenant. Cela n'a pas toujours été le cas.

Vous devriez pouvoir contourner ce problème en vous assurant que les fichiers utilisent différents chemins du répertoire intermédiaire, mais il s’agit là d’un simple bidouillage autour de quelque chose que devrait être possible.

Bien sûr, vous pouvez toujours créer un rapport de bogue ou une demande de fonctionnalité sur Microsoft Connect

8
jalf

Les autres solutions de ce fil souffrent des problèmes liés à RelativeDir ../ .. et de la nécessité de définir les choses manuellement sur chaque fichier source. 

Sans oublier, ils épave/MP. Toute solution spécifiant un .obj exact pour% (ObjectFileName) donnera un fichier/Fo différent pour chaque fichier .cpp (pour le mapper sur un fichier .obj spécifique) transmis à CL.exe et Visual Studio ne pourra donc pas les traiter par lots. . Sans regrouper plusieurs fichiers .cpp avec des lignes de commande identiques (y compris/Fo), le fichier/MP ne peut pas fonctionner.

Voici une nouvelle approche. Cela fonctionne sur vs2010 à vs2015 au moins. Ajoutez ceci à votre vcxproj dans le <projet>

<!-- ================ 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 ================ -->

Et puis modifiez <projet> pour qu'il se lise:

<Project InitialTargets="UNDUPOBJ" ...

Le résultat sera quelque chose comme myproj/src/a/x.cpp et myproj/src/b/x.cpp en compilant pour Debug/0/x.obj et Debug/1/x.obj. Les noms relatifs ne sont pas employés et ne posent donc pas de problème.

De plus, dans ce cas, seuls deux/Fo différents seront transmis à CL.exe: Debug/0/et Debug/1 /. Par conséquent, pas plus de deux lots ne seront envoyés à CL.exe, ce qui permettra au/MP de fonctionner plus efficacement.

Vous pouvez également baser les sous-répertoires .obj sur les sous-répertoires .cpp ou faire en sorte que le nom du fichier .obj contienne un mémento du répertoire .cpp original afin que vous puissiez facilement voir un mappage .cpp -> .obj,/Fo et donc moins de dosage. Les travaux futurs pourraient vider un fichier de mappage pour une référence rapide, peut-être.

Voir ceci pour plus de détails sur/MP et le traitement par lots: MSVC10/MP ne construit pas de multicœurs dans les dossiers d’un projet

Cela fait un moment que je teste cela en production sur vs2010 et vs2015 sur diverses chaînes d'outils. Il semble à l'épreuve des balles, mais il y a toujours une chance que cela interagisse mal avec d'autres personnalisations msbuild ou chaînes d'outils exotiques.

À partir de vs2015, si vous recevez un avertissement "MSB8027: deux fichiers ou plus portant le nom X.cpp produiront des sorties au même emplacement", vous pouvez les ajouter à votre projet ou aux fichiers msbuild:

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

Voir plus à https://connect.Microsoft.com/VisualStudio/feedback/details/797460/incorrect-warning-msb8027-reported-for-files-excluded-from-build et Comment supprimer MSBuild Attention

6
zeromus

Notez que dans mon cas (Visual Studio 2013 avec les outils de la plate-forme VS2010), l’utilisation de $ (IntDir) \% (RelativeDir) ne fonctionne pas correctement et ignore le répertoire intermédiaire qui génère des erreurs d’éditeur de liens lors de la création de Les fichiers objet de chaque configuration (c'est-à-dire, Debug et Release) sont placés dans le même dossier. Celles-ci disparaissent si vous nettoyez le projet lors du changement de configuration.

Exemple d'erreur:

MSVCRTD.lib (MSVCR100D.dll): erreur LNK2005: _fclose déjà défini dans LIBCMTD.lib (fclose.obj)

Solution:

$ (IntDir) \% (Répertoire)

Pour résoudre ce problème, je devais utiliser $ (IntDir) \% (Directory), qui plaçait correctement tous les fichiers * .obj dans le répertoire intermédiaire et permettait de créer et de lier plusieurs configurations sans nettoyage. Le seul inconvénient est que la hiérarchie complète (potentiellement longue) des dossiers dans laquelle se trouvent vos fichiers sera complètement recréée dans le dossier Debug/Release/etc.

3
syplex

utilisez les Propriétés de configuration> C/C++> Fichiers Ouptut> $ (IntDir) \% (RelativeDir) \% (Nom du fichier)

cela dupliquera la structure du fichier source sous le répertoire de débogage et déposera le fichier objet pour chaque répertoire dans un dossier du même nom sous le répertoire Debug  

2
Christoph

Il se peut également que vous créiez un fichier .cpp dans Visual Studio et que vous le renommiez plus tard en .h. Bien que le fichier soit renommé, visual studio le compile toujours en tant que fichier cpp. Par conséquent, deux fichiers obj sont créés et l’avertissement de l’éditeur de liens affiché.

1
Povilas

La solution% (RelativeDir) ne fonctionne que pour Visual Studo 2010.

Pour Visual Studio 2008, vous devez cliquer avec le bouton droit de la souris sur chaque nom de fichier .cpp en double et choisir "Propriétés". Cela peut ne pas sembler évident, mais vous pouvez réellement modifier la section "Propriétés de configuration -> C/C++ -> Fichiers Ouptut" pour chaque fichier.

Ajoutez un sous-dossier au paramètre "Nom de fichier de l'objet" de chacun des fichiers .cpp en double. Notez qu'il n'est pas nécessaire de modifier les fichiers .h car le fichier .cpp détermine la sortie du fichier .obj.

Par exemple, si votre projet contient deux fichiers en conflit:

internal\example.cpp base\example.cpp

Vous définiriez le "Nom de fichier de l'objet" pour chacun comme suit:

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

Vous devrez le faire pour toutes les configurations Release/Debug etc.

0
user3700221