web-dev-qa-db-fra.com

Quelle est la meilleure pratique pour "Copy Local" et les références de projet?

J'ai un gros fichier de solution c # (~ 100 projets) et j'essaie d'améliorer les temps de construction. Je pense que "Copy Local" est souvent une perte de temps pour nous, mais je m'interroge sur les meilleures pratiques.

Dans notre .sln, nous avons l’application A dépendant de l’Assemblée B qui dépend de l’Assemblée C. Dans notre cas, il existe des dizaines de "B" et une poignée de "C". Comme ils sont tous inclus dans le fichier .sln, nous utilisons des références de projet. Tous les assemblys actuellement construits dans $ (SolutionDir)/Debug (ou Release).

Par défaut, Visual Studio marque ces références de projet comme "Copie locale", ce qui entraîne la copie de chaque "C" dans $ (SolutionDir)/Debug une fois pour chaque "B" créé. Cela semble inutile. Qu'est-ce qui peut mal se passer si je désactive simplement "Copier Local"? Que font les autres utilisateurs de grands systèmes?

SUIVRE:

De nombreuses réponses suggèrent de diviser la construction en fichiers .sln plus petits ... Dans l'exemple ci-dessus, je construirais d'abord les classes de base "C", puis l'ensemble des modules "B", puis quelques applications, " UNE". Dans ce modèle, je dois avoir des références non-projet à C de B. Le problème que je rencontre ici est que "Debug" ou "Release" soit incrusté dans le chemin des indices et que je finis par construire mes versions "B" contre les versions de débogage de "C". 

Pour ceux d’entre vous qui scindez l’accumulation en plusieurs fichiers .sln, comment gérez-vous ce problème?

153
Dave Moore

Dans un projet précédent, je travaillais avec une solution volumineuse avec des références de projet et me heurtais également à un problème de performances. La solution était triple:

  1. Définissez toujours la propriété Copy Local sur false et appliquez-la via une étape msbuild personnalisée.

  2. Définissez le répertoire de sortie de chaque projet dans le même répertoire (de préférence par rapport à $ (SolutionDir)

  3. Les cibles cs par défaut fournies avec la structure calculent l'ensemble des références à copier dans le répertoire de sortie du projet en cours de construction. Comme cela nécessite de calculer une fermeture transitive sous la relation 'Références', cela peut devenir VERY coûteux. Ma solution consistait à redéfinir la cible GetCopyToOutputDirectoryItems dans un fichier de cibles commun (par exemple, Common.targets) importé dans chaque projet après l'importation de Microsoft.CSharp.targets. Il en résulte que chaque fichier de projet a l'aspect suivant:

    <Project DefaultTargets="Build" xmlns="http://schemas.Microsoft.com/developer/msbuild/2003">
      <PropertyGroup>
        ... snip ...
      </ItemGroup>
      <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
      <Import Project="[relative path to Common.targets]" />
      <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
           Other similar extension points exist, see Microsoft.Common.targets.
      <Target Name="BeforeBuild">
      </Target>
      <Target Name="AfterBuild">
      </Target>
      -->
    </Project>
    

Cela a réduit notre temps de construction à un moment donné de quelques heures (principalement en raison de contraintes de mémoire) à quelques minutes.

La GetCopyToOutputDirectoryItems redéfinie peut être créée en copiant les lignes 2 438 à 2 450 et 2 474 à 2 524 de C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Microsoft.Common.targets dans Common.targets.

Pour être complet, la définition cible résultante devient alors:

<!-- This is a modified version of the Microsoft.Common.targets
     version of this target it does not include transitively
     referenced projects. Since this leads to enormous memory
     consumption and is not needed since we use the single
     output directory strategy.
============================================================
                    GetCopyToOutputDirectoryItems

Get all project items that may need to be transferred to the
output directory.
============================================================ -->
<Target
    Name="GetCopyToOutputDirectoryItems"
    Outputs="@(AllItemsFullPathWithTargetPath)"
    DependsOnTargets="AssignTargetPaths;_SplitProjectReferencesByFileExistence">

    <!-- Get items from this project last so that they will be copied last. -->
    <CreateItem
        Include="@(ContentWithTargetPath->'%(FullPath)')"
        Condition="'%(ContentWithTargetPath.CopyToOutputDirectory)'=='Always' or '%(ContentWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"
            >
        <Output TaskParameter="Include" ItemName="AllItemsFullPathWithTargetPath"/>
        <Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectoryAlways"
                Condition="'%(ContentWithTargetPath.CopyToOutputDirectory)'=='Always'"/>
        <Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectory"
                Condition="'%(ContentWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"/>
    </CreateItem>

    <CreateItem
        Include="@(_EmbeddedResourceWithTargetPath->'%(FullPath)')"
        Condition="'%(_EmbeddedResourceWithTargetPath.CopyToOutputDirectory)'=='Always' or '%(_EmbeddedResourceWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"
            >
        <Output TaskParameter="Include" ItemName="AllItemsFullPathWithTargetPath"/>
        <Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectoryAlways"
                Condition="'%(_EmbeddedResourceWithTargetPath.CopyToOutputDirectory)'=='Always'"/>
        <Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectory"
                Condition="'%(_EmbeddedResourceWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"/>
    </CreateItem>

    <CreateItem
        Include="@(Compile->'%(FullPath)')"
        Condition="'%(Compile.CopyToOutputDirectory)'=='Always' or '%(Compile.CopyToOutputDirectory)'=='PreserveNewest'">
        <Output TaskParameter="Include" ItemName="_CompileItemsToCopy"/>
    </CreateItem>
    <AssignTargetPath Files="@(_CompileItemsToCopy)" RootFolder="$(MSBuildProjectDirectory)">
        <Output TaskParameter="AssignedFiles" ItemName="_CompileItemsToCopyWithTargetPath" />
    </AssignTargetPath>
    <CreateItem Include="@(_CompileItemsToCopyWithTargetPath)">
        <Output TaskParameter="Include" ItemName="AllItemsFullPathWithTargetPath"/>
        <Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectoryAlways"
                Condition="'%(_CompileItemsToCopyWithTargetPath.CopyToOutputDirectory)'=='Always'"/>
        <Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectory"
                Condition="'%(_CompileItemsToCopyWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"/>
    </CreateItem>

    <CreateItem
        Include="@(_NoneWithTargetPath->'%(FullPath)')"
        Condition="'%(_NoneWithTargetPath.CopyToOutputDirectory)'=='Always' or '%(_NoneWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"
            >
        <Output TaskParameter="Include" ItemName="AllItemsFullPathWithTargetPath"/>
        <Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectoryAlways"
                Condition="'%(_NoneWithTargetPath.CopyToOutputDirectory)'=='Always'"/>
        <Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectory"
                Condition="'%(_NoneWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"/>
    </CreateItem>
</Target>

Avec cette solution de contournement en place, j'ai trouvé qu'il était pratique d'avoir plus de 120 projets dans une solution. L'avantage principal est que l'ordre de construction des projets peut toujours être déterminé par VS au lieu de le faire à la main en fractionnant votre solution. .

84
Bas Bossink

Je vous suggère de lire les articles de Patric Smacchia sur ce sujet:

Les projets CC.Net VS reposent sur l'option d'assemblage de la référence locale de copie définie sur true. [...] Non seulement cela augmente considérablement le temps de compilation (x3 dans le cas de NUnit), mais cela gâche également votre environnement de travail. Enfin et surtout, cela introduit le risque de problèmes de version. Btw, NDepend émettra un avertissement s’il trouve 2 assemblys dans 2 répertoires différents portant le même nom, mais pas le même contenu ou la même version.

La bonne chose à faire est de définir 2 répertoires $ RootDir $\bin\Debug et $ RootDir $\bin\Release, et de configurer vos projets VisualStudio pour qu'ils émettent des assemblys dans ces répertoires. Toutes les références de projet doivent référencer les assemblys dans le répertoire Debug.

Vous pouvez également lire cet article pour vous aider à réduire le nombre de vos projets et à améliorer votre temps de compilation.

31
Julien Hoarau

Je suggère d'avoir copy local = false pour presque tous les projets, sauf celui qui se trouve en haut de l'arbre de dépendance. Et pour toutes les références dans celle du haut, copiez local = true. Je vois beaucoup de gens suggérer de partager un répertoire de sortie; Je pense que c'est une idée horrible basée sur l'expérience. Si votre projet de démarrage contient des références à une dll, tout autre projet contient une référence à vous, à un moment donné, rencontrez une violation d'accès\partage même si copy local = false sur tout et votre construction échouera. Cette question est très agaçante et difficile à repérer. Je suggère tout à fait d'éviter les répertoires de sortie des fragments et de placer le projet en haut de la chaîne de dépendance en écrivant les assemblys nécessaires dans le dossier correspondant. Si vous n'avez pas de projet au sommet, alors je vous suggérerais une copie post-build pour que tout soit au bon endroit. Aussi, je voudrais essayer de garder à l'esprit la facilité de débogage. Tous les projets exe je laisse encore copy local = true pour que l'expérience de débogage F5 fonctionne.

23
Aaron Stainback

Vous avez raison. CopyLocal va absolument tuer vos temps de construction. Si vous avez une grande arborescence de sources, vous devez désactiver CopyLocal. Malheureusement, il n’est pas aussi facile qu’il devrait être de le désactiver proprement. J'ai répondu à cette question précise sur la désactivation de CopyLocal dans Comment remplacer le paramètre CopyLocal (privé) pour les références dans .NET à partir de MSBUILD . Vérifiez-le. Ainsi que Les meilleures pratiques pour les solutions volumineuses dans Visual Studio (2008).

Voici quelques informations supplémentaires sur CopyLocal tel que je le vois.

CopyLocal a été réellement implémenté pour supporter le débogage local. Lorsque vous préparez votre application pour l'empaquetage et le déploiement, vous devez créer vos projets dans le même dossier de sortie et vous assurer de disposer de toutes les références dont vous avez besoin.

J'ai écrit sur la façon de créer de grands arbres source dans l'article MSBuild: Meilleures pratiques pour créer des versions fiables, deuxième partie .

10

À mon avis, avoir une solution avec 100 projets est une grosse erreur. Vous pourriez probablement diviser votre solution en petites unités logiques valides, simplifiant ainsi la maintenance et la génération.

9
Bruno Shine

Je suis surpris que personne n'ait mentionné l'utilisation de liens durs. Au lieu de copier les fichiers, il crée un lien dur au fichier d'origine. Cela économise de l'espace disque et accélère considérablement la construction. Cela peut être activé sur la ligne de commande avec les propriétés suivantes: 

/ p: CreateHardLinksForAdditionalFilesIfPossible = true; CreateHardLinksForCopyAdditionalFilesIfPossible = true; CreateHardLinksForCopyFilesToOutputDirectoryIfPossible = true; CreateHardLinksForCopyLocalIfPossible = true; CreateHardLinksForListe

Vous pouvez également l'ajouter à un fichier d'importation central afin que tous vos projets puissent également bénéficier de cet avantage.

6
Matt Slagle

Si vous avez la structure de dépendance définie via les références de projet ou les dépendances au niveau de la solution, vous pouvez sans risque activer la fonction "Copier en local". Je dirais même que c’est une bonne pratique à suivre. via/maxcpucount) sans que différents processus ne se déclenchent les uns sur les autres lors de la tentative de copie des assemblys référencés.

5

notre "meilleure pratique" consiste à éviter les solutions avec de nombreux projets ..__ Nous avons un répertoire nommé "matrix" avec les versions actuelles des assemblys, et toutes les références proviennent de ce répertoire. Si vous modifiez un projet et que vous pouvez dire "la modification est terminée", vous pouvez copier l'assembly dans le répertoire "matrix". Ainsi, tous les projets qui dépendent de cet assemblage auront la version actuelle (= la plus récente).

Si vous avez peu de projets en solution, le processus de construction est beaucoup plus rapide.

Vous pouvez automatiser l'étape "Copier l'assemblage dans le répertoire de la matrice" à l'aide de macros Visual Studio ou via l'option "menu -> outils -> outils externes ...".

4
TcKs

Vous n'avez pas besoin de changer les valeurs CopyLocal. Tout ce que vous avez à faire est de prédéfinir un $ (OutputPath) commun pour tous les projets de la solution et de prérégler $ (UseCommonOutputDirectory) sur true. Voir ceci: http://blogs.msdn.com/b/kirillosenkov/archive/2015/04/04/using-a-common-intermediate-and-output-directory-for-your-solution. aspx

3
Sheen

Définir CopyLocal = false réduira le temps de génération, mais peut entraîner différents problèmes lors du déploiement.

Il existe de nombreux scénarios, lorsque vous devez laisser l'option Copie locale »laissée à True, par exemple. 

  • Projets de premier niveau, 
  • Dépendances de second niveau, 
  • DLL appelées par réflexion

Les problèmes possibles décrits dans les questions SO
" Quand faut-il copier-local pour true et quand ne pas le faire? ",
" Message d'erreur" Impossible de charger un ou plusieurs des types demandés. Récupérez la propriété LoaderExceptions pour plus d'informations. " "
et aaron-stainback 's répondez pour cette question.

Mon expérience avec la définition de CopyLocal = false N'A PAS réussi. Voir mon article de blog "Ne pas modifier" les références de projet Copier en local "sont fausses, à moins de comprendre les sous-séquences."

Le temps nécessaire pour résoudre les problèmes donne trop de poids aux avantages de la définition de copyLocal = false.

2
Michael Freidgeim

Ce n'est peut-être pas la meilleure pratique, mais c'est comme ça que je travaille. 

J'ai remarqué que le C++ géré dépose tous ses fichiers binaires dans $ (SolutionDir)/'DebugOrRelease' . J'ai donc vidé tous mes projets C #. J'ai également désactivé la "copie locale" de toutes les références aux projets de la solution. J'ai eu une amélioration notable du temps de construction de ma petite solution de 10 projets. Cette solution est un mélange de projets C #, C++ gérés, C++ natif, Webservice C # et d’installateur. 

Quelque chose est peut-être cassé, mais comme c'est la seule façon de travailler, je ne le remarque pas.

Il serait intéressant de savoir ce que je casse.

1
jyoung

J'ai tendance à créer un répertoire commun (par exemple, ..\bin) afin de pouvoir créer de petites solutions de test.

1
kenny

Vous pouvez essayer d'utiliser un dossier dans lequel tous les assemblys partagés entre les projets seront copiés, puis créez une variable d'environnement DEVPATH et définissez-la. 

<developmentMode developerInstallation="true" /> 

dans le fichier machine.config sur le poste de travail de chaque développeur. La seule chose à faire est de copier toute nouvelle version de votre dossier dans laquelle pointe la variable DEVPATH.

Divisez également votre solution en plusieurs solutions plus petites, si possible.

1
Aleksandar

Si la référence n'est pas contenue dans le GAC, nous devons définir la copie locale sur True pour que l'application fonctionne. Si nous sommes sûrs que la référence sera préinstallée dans le GAC, vous pouvez la définir sur false.

0
SharpGobi

Si vous souhaitez disposer d'un emplacement central pour référencer une DLL à l'aide de copy local false, vous échouerez sans le GAC, à moins que vous ne procédiez ainsi.

http://nbaked.wordpress.com/2010/03/28/gac-alternative/

0
user306031

Bien, je ne sais certainement pas comment les problèmes se résolvent, mais j’ai eu un contact avec une solution de construction qui s’est utilisée de telle sorte que tous les fichiers créés soient placés sur un ramdisk à l’aide de liens symboliques

  • c:\solution folder\bin -> disque virtuel r:\solution folder\bin \
  • c:\solution folder\obj -> disque virtuel r:\solution folder\obj \

  • Vous pouvez également indiquer à visual studio le répertoire temporaire qu’il peut utiliser pour la construction. 

En fait, ce n’est pas tout ce que cela a fait. Mais cela a vraiment touché ma compréhension de la performance. 
100% d’utilisation du processeur et un énorme projet en moins de 3 minutes avec toutes les dépendances. 

0
user7179930

Vous pouvez faire en sorte que vos références de projets pointent vers les versions de débogage des dll . Par la suite, vous pouvez définir le /p:Configuration=Release sur votre script msbuild, afin de disposer d’une version finale de votre application et de tous les assemblys satellites.

0
Bruno Shine

Généralement, vous n'avez besoin de copier Local que si vous souhaitez que votre projet utilise la DLL qui se trouve dans votre Corbeille par rapport à ce qui est ailleurs (le GAC, d'autres projets, etc.).

J'aurais tendance à être d'accord avec les autres pour dire que vous devriez également essayer, dans la mesure du possible, de démanteler cette solution.

Vous pouvez également utiliser Configuration Manager pour créer vous-même différentes configurations de construction au sein de cette solution qui ne construira que des ensembles de projets donnés.

Il semblerait étrange que les 100 projets s'appuient les uns sur les autres. Vous devriez donc pouvoir le diviser ou utiliser Configuration Manager pour vous aider.

0
CubanX