Quelle est la meilleure pratique pour «Copy Local» et avec des références de projet?

J’ai un grand fichier de solution c # (~ 100 projets) et j’essaie d’améliorer les temps de construction. Je pense que «Copy Local» est un gaspillage dans de nombreux cas pour nous, mais je me pose des questions sur les meilleures pratiques.

Dans notre .sln, nous avons l’application A en fonction de l’assemblage B qui dépend de l’assemblage C. Dans notre cas, il y a 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 une copie de chaque “C” dans $ (SolutionDir) / Debug pour chaque “B” généré. Cela semble gaspiller. Qu’est-ce qui peut mal tourner si je ne fais que désactiver “Copy Local”? Que font les autres personnes avec de grands systèmes?

SUIVRE:

Beaucoup de 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», suivies de la plupart des modules «B», puis de quelques applications » UNE”. Dans ce modèle, j’ai besoin de références non-projet à C à partir de B. Le problème que je rencontre est que “Debug” ou “Release” est intégré dans le chemin des indices et que je finis par construire mes versions de “B” contre les versions de débogage de “C”.

Pour ceux d’entre vous qui ont divisé la compilation en plusieurs fichiers .sln, comment gérez-vous ce problème?

Dans un projet précédent, j’ai travaillé avec une grande solution avec des références de projet et j’ai également rencontré un problème de performance. La solution était sortingple:

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

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

  3. Les cibles cs par défaut fournies avec l’infrastructure 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 très coûteux. Ma solution de contournement pour cela consistait à redéfinir la cible GetCopyToOutputDirectoryItems dans un fichier cible commun (par exemple, Common.targets ) importé dans chaque projet après l’importation de Microsoft.CSharp.targets . Résultat dans chaque fichier de projet pour ressembler à ceci:

       ... snip ...      

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

GetCopyToOutputDirectoryItems redéfini peut être créé 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 d’objective qui en résulte devient alors:

                               

Avec cette solution de contournement en place, j’ai trouvé qu’il était possible d’avoir jusqu’à 120 projets en une seule solution, ce qui a pour principal avantage que l’ordre de construction des projets peut toujours être déterminé par VS au lieu de le faire manuellement. .

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

  • Partitionnement de votre base de code via des assemblys .NET et des projets Visual Studio -> Chaque projet Visual Studio doit-il vraiment se trouver dans son propre assemblage? Et que signifie “Copier Local = Vrai”?
  • Leçons tirées de la base de code NUnit -> L’option True VisualStudio Project Reference + Copy Local est maléfique! )
  • Analyse de la base de code de CruiseControl.NET -> Utilisation incorrecte de l’option de l’assembly de référence locale de copie définie sur True)

Les projets CC.Net VS s’appuient sur l’option de copie locale de l’assembly de référence définie sur true. […] Non seulement cela augmente considérablement le temps de compilation (x3 dans le cas de NUnit), mais cela perturbe également votre environnement de travail. Last but not least, cela introduit le risque de problèmes de gestion des versions. Btw, NDepend émettra un avertissement s’il a trouvé 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 faire référence à des assemblys dans le répertoire de débogage.

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

Je suggère d’avoir copy local = false pour presque tous les projets sauf celui qui se trouve en haut de l’arborescence des dépendances. Et pour toutes les références dans celle en haut, définissez copy local = true. Je vois beaucoup de gens suggérant 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 que tout autre projet contient une référence, vous rencontrerez, à un moment donné, une violation d’access \ partage même si la copie locale = faux sur tout et votre génération échouera. Ce problème est très ennuyeux et difficile à localiser. Je suggère complètement de restr à l’écart d’un répertoire de sortie de partition et, au lieu d’avoir le projet en haut de la chaîne de dépendance, écrivez les assemblages nécessaires dans le dossier correspondant. Si vous n’avez pas de projet en haut de la liste, 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é du débogage. Tous les projets exe que je laisse toujours copy local = true pour que l’expérience de débogage F5 fonctionne.

Vous avez raison. CopyLocal va absolument tuer vos temps de construction. Si vous avez un grand arbre source, vous devez désactiver CopyLocal. Malheureusement, ce n’est pas aussi facile que cela 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 grandes solutions dans Visual Studio (2008).

Voici quelques informations sur CopyLocal telles que je les vois.

CopyLocal a été implémenté pour supporter le débogage local. Lorsque vous préparez votre application pour le conditionnement 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 nécessaires.

J’ai écrit sur la manière de gérer la création de grandes arborescences sources dans l’article MSBuild: Meilleures pratiques pour la création de builds fiables, Partie 2 .

À 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 les versions.

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

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

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

Si vous avez défini la structure de dépendances via des références de projet ou via des dépendances au niveau de la solution, vous pouvez utiliser “Copy Local”. Je vous dirais même que cela vous permettra d’utiliser MSBuild 3.5 pour exécuter votre build en parallèle ( via / maxcpucount) sans que des processus différents se déclenchent lors de la tentative de copie des assemblys référencés.

notre “meilleure pratique” est d’éviter les solutions avec de nombreux projets. Nous avons un répertoire nommé “masortingce” avec les versions actuelles des assemblages, et toutes les références proviennent de ce répertoire. Si vous modifiez un projet et que vous pouvez dire “maintenant que le changement est terminé”, vous pouvez copier l’assemblage dans le répertoire “masortingx”. 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 masortingce” en utilisant des macros visuelles ou avec “menu -> outils -> outils externes …”.

Set CopyLocal = false réduira le temps de construction, mais peut entraîner différents problèmes lors du déploiement.

Il existe de nombreux scénarios, lorsque vous devez avoir Copy Local ‘à gauche, par exemple

  • Projets de haut niveau,
  • Dépendances de second niveau,
  • DLL appelées par reflection

Les problèmes possibles décrits dans les questions de SO
” Quand copy-local doit-il être défini sur true et quand ne devrait-il pas l’être? “,
Msgstr ” Message d’erreur” Impossible de charger un ou plusieurs types requirejs. Récupérez la propriété LoaderExceptions pour plus d’informations. ” ”
et la réponse d’ Aaron Stainback à cette question.

Mon expérience avec la configuration de CopyLocal = false n’a PAS réussi. Voir mon article de blog “NE PAS modifier les références de projet” Copie locale “en false, à moins de comprendre les sous-séquences.”

Le temps de résoudre les problèmes de surpoids sur les avantages de la définition de copyLocal = false.

Vous n’avez pas besoin de modifier les valeurs CopyLocal. Il vous suffit de prédéfinir un $ commun (OutputPath) pour tous les projets de la solution et de prédéfinir $ (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

J’ai tendance à créer un répertoire commun (par exemple .. \ bin), donc je peux créer de petites solutions de test.

Vous pouvez essayer d’utiliser un dossier dans lequel tous les assemblys partagés entre projets seront copiés, puis créer une variable d’environnement DEVPATH et définir

dans le fichier machine.config sur le poste de travail de chaque développeur. La seule chose que vous devez faire est de copier toute nouvelle version dans votre dossier où DEVPATH variable pointe.

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

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

J’ai remarqué que Managed C ++ vide tous ses fichiers binarys dans $ (SolutionDir) / ‘DebugOrRelease’. J’ai donc jeté tous mes projets C # là aussi. J’ai également désactivé la “copie locale” de toutes les références aux projets de la solution. J’avais une amélioration notable du temps de construction dans ma solution de petit projet. Cette solution est un mélange de projets C #, C ++ géré, C ++ natif, C # web et de projets d’installation.

Peut-être que quelque chose est cassé, mais puisque c’est la seule façon dont je travaille, je ne le remarque pas.

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

En règle générale, il vous suffit de copier Local si vous souhaitez que votre projet utilise la DLL qui se trouve dans votre corbeille par rapport à ce qui existe ailleurs (le GAC, d’autres projets, etc.).

J’aurais tendance à être d’accord avec les autres personnes sur le fait que vous devriez également essayer, si possible, de briser cette solution.

Vous pouvez également utiliser Configuration Manager pour créer différentes configurations de construction dans cette solution unique qui ne génère que des ensembles de projets donnés.

Il semblerait étrange que les 100 projets reposent les uns sur les autres, vous devriez donc pouvoir soit les décomposer, soit utiliser Configuration Manager pour vous aider.

Vous pouvez avoir vos références de projets pointant vers les versions de débogage des DLL. Que sur votre script msbuild, vous pouvez définir /p:Configuration=Release , vous aurez donc une version de votre application et tous les assemblys satellites.

Si vous voulez avoir un endroit central pour référencer une DLL en utilisant copy local false, vous échouerez sans le GAC, sauf si vous le faites.

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

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 sums sûrs que la référence sera préinstallée dans le GAC, elle peut être définie sur false.

Eh bien, je ne sais certainement pas comment les problèmes fonctionnent, mais j’ai eu un contact avec une solution de construction qui s’est aidée de telle sorte que tous les fichiers créés sont placés sur un disque virtuel à l’aide de liens symboliques .

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

  • Vous pouvez également indiquer à Visual Studio quel répertoire temporaire il peut utiliser pour la construction.

En fait, ce n’était pas tout ce que ça faisait. Mais cela a vraiment frappé 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.