it-swarm.com.de

Was ist die bewährte Methode für "Lokal kopieren" und mit Projektreferenzen?

Ich habe eine große C # -Lösungsdatei (~ 100 Projekte) und versuche, die Buildzeiten zu verbessern. Ich denke, dass "Copy Local" in vielen Fällen für uns verschwenderisch ist, aber ich wundere mich über Best Practices.

In unserer .sln haben wir die Anwendung A abhängig von Assembly B, die von Assembly C abhängt. In unserem Fall gibt es Dutzende von "B" und eine Handvoll "C". Da diese alle in der .sln enthalten sind, verwenden wir Projektreferenzen. Alle Assemblys sind derzeit in $ (SolutionDir)/Debug (oder Release) integriert.

Standardmäßig kennzeichnet Visual Studio diese Projektverweise als "Copy Local", wodurch jedes "C" für jedes erstellte "B" einmal in $ (SolutionDir)/Debug kopiert wird. Das scheint verschwenderisch zu sein. Was kann schief gehen, wenn ich einfach "Copy Local" ausschalte? Was machen andere Leute mit großen Systemen?

NACHVERFOLGEN:

Viele Antworten deuten darauf hin, dass der Build in kleinere .sln-Dateien aufgeteilt wird. Im obigen Beispiel würde ich zuerst die Basisklassen "C" erstellen, gefolgt von den meisten Modulen "B" und dann einigen Anwendungen. " EIN". In diesem Modell brauche ich nicht-projektbezogene Verweise auf C von B. Das Problem, dem ich dort begegne, ist, dass "Debug" oder "Release" in den Hinweispfad eingebettet wird und ich meine Build-Versionen von "B" aufbaue gegen Debug-Builds von "C". 

Wie bewältigen Sie dieses Problem, wenn Sie den Aufbau in mehrere .sln-Dateien aufteilen?

153
Dave Moore

In einem früheren Projekt habe ich mit einer großen Lösung mit Projektreferenzen gearbeitet und auch auf ein Leistungsproblem gestoßen. Die Lösung war dreifach:

  1. Setzen Sie die Copy Local-Eigenschaft immer auf false und erzwingen Sie dies über einen benutzerdefinierten Msbuild-Schritt

  2. Legen Sie das Ausgabeverzeichnis für jedes Projekt auf das gleiche Verzeichnis fest (vorzugsweise relativ zu $ ​​(SolutionDir)).

  3. Die standardmäßigen cs-Ziele, die mit dem Framework ausgeliefert werden, berechnen die Menge der Referenzen, die in das Ausgabeverzeichnis des aktuell erstellten Projekts kopiert werden sollen. Da dies die Berechnung eines transitiven Abschlusses unter der Beziehung "Referenzen" erfordert, kann dies VERY teuer werden. Mein Workaround war, das GetCopyToOutputDirectoryItems-Ziel in einer gemeinsamen Zieldatei (z. B. Common.targets) neu zu definieren, die nach dem Import des Microsoft.CSharp.targets in jedes Projekt importiert wird. Das Ergebnis, dass jede Projektdatei folgendermaßen aussieht: 

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

Dies verkürzte unsere Bauzeit zu einem bestimmten Zeitpunkt von einigen Stunden (hauptsächlich aufgrund von Speicherbeschränkungen) auf einige Minuten.

Die neu definierte GetCopyToOutputDirectoryItems kann durch Kopieren der Zeilen 2.438–2.450 und 2.474–2.524 von C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Microsoft.Common.targets in Common.targets erstellt werden.

Der Vollständigkeit halber lautet die resultierende Zieldefinition dann:

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

Mit dieser Problemumgehung fand ich es praktikabel, bis zu 120 Projekte in einer Lösung zu haben. Dies hat den Hauptvorteil, dass die Build-Reihenfolge der Projekte immer noch von VS festgelegt werden kann, anstatt dies manuell durch Aufteilen der Lösung zu tun .

83
Bas Bossink

Ich schlage vor, Patric Smacchias Artikel zu diesem Thema zu lesen:

CC.Net VS-Projekte sind darauf angewiesen, dass die Assembly-Option zum Kopieren der lokalen Referenz auf true gesetzt ist. [...] Dies erhöht nicht nur die Kompilierungszeit signifikant (x3 im Fall von NUnit), sondern auch Ihre Arbeitsumgebung. Nicht zuletzt birgt dies die Gefahr der Versionierung potenzieller Probleme. Übrigens gibt NDepend eine Warnung aus, wenn 2 Assemblys in 2 verschiedenen Verzeichnissen mit demselben Namen, aber nicht demselben Inhalt oder derselben Version gefunden werden.

Richtig ist, dass Sie 2 Verzeichnisse $ RootDir $\bin\Debug und $ RootDir $\bin\Release definieren und Ihre VisualStudio-Projekte so konfigurieren, dass Assemblys in diesen Verzeichnissen ausgegeben werden. Alle Projektverweise sollten auf Assemblys im Debug-Verzeichnis verweisen.

Sie könnten auch diesen Artikel lesen, um Ihnen zu helfen, die Anzahl Ihrer Projekte zu reduzieren und die Zusammenstellungszeit zu verbessern.

31
Julien Hoarau

Ich empfehle, für fast alle Projekte eine Kopie local = false zu verwenden, mit Ausnahme des Projekts, das sich im oberen Teil des Abhängigkeitsbaums befindet. Und für alle Verweise im obersten Satz kopieren Sie local = true. Ich sehe viele Leute, die vorschlagen, ein Ausgabeverzeichnis gemeinsam zu nutzen. Ich denke, das ist eine schreckliche Idee, die auf Erfahrung beruht. Wenn Ihr Startprojekt Verweise auf eine DLL enthält, die bei einem anderen Projekt einen Verweis auf Sie enthalten, wird an einem bestimmten Punkt eine Zugriffsverletzung auftreten, auch wenn Copy local = false für alles und Ihr Build fehlschlägt. Diese Ausgabe ist sehr ärgerlich und schwer zu finden. Ich empfehle Ihnen, sich von einem Shard-Ausgabeverzeichnis fernzuhalten, und anstatt das Projekt oben in der Abhängigkeitskette zu haben, schreiben Sie die benötigten Assemblys in den entsprechenden Ordner. Wenn Sie kein Projekt an der "Spitze" haben, würde ich eine Nachbau-Kopie vorschlagen, um alles an den richtigen Ort zu bringen. Ich würde auch versuchen, die Leichtigkeit des Debugging zu berücksichtigen. Alle Exe-Projekte, die ich noch belasse, kopieren local = true, damit das Debugging von F5 funktioniert.

23
Aaron Stainback

Du hast Recht. CopyLocal wird Ihre Buildzeiten absolut beenden. Wenn Sie über einen großen Quellbaum verfügen, sollten Sie CopyLocal deaktivieren. Leider ist es nicht so einfach wie es sein sollte, es sauber zu deaktivieren. Ich habe diese genaue Frage zum Deaktivieren von CopyLocal bei Wie überschreibe ich die CopyLocal-Einstellung (privat) für Verweise in .NET von MSBUILD Hör zu. Sowie Best Practices für große Lösungen in Visual Studio (2008).

Hier sind einige weitere Informationen zu CopyLocal, wie ich es sehe.

CopyLocal wurde wirklich implementiert, um das lokale Debugging zu unterstützen. Wenn Sie Ihre Anwendung für das Packen und Bereitstellen vorbereiten, sollten Sie Ihre Projekte im selben Ausgabeordner erstellen und sicherstellen, dass Sie dort alle erforderlichen Referenzen haben.

In Artikel MSBuild: Best Practices für das Erstellen zuverlässiger Builds, Teil 2 wurde beschrieben, wie mit dem Erstellen großer Quellbäume umgegangen wird.

10

Eine Lösung mit 100 Projekten ist meiner Meinung nach ein großer Fehler. Sie könnten Ihre Lösung wahrscheinlich in gültige kleine logische Einheiten aufteilen, was sowohl die Wartung als auch die Erstellung vereinfacht.

9
Bruno Shine

Ich bin überrascht, dass niemand die Verwendung von Hardlinks erwähnt hat. Anstatt die Dateien zu kopieren, wird ein Hardlink zur Originaldatei erstellt. Dies spart Speicherplatz und beschleunigt den Aufbau erheblich. Dies kann in der Befehlszeile mit den folgenden Eigenschaften aktiviert werden: 

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

Sie können dies auch zu einer zentralen Importdatei hinzufügen, damit auch alle Ihre Projekte diesen Vorteil nutzen können.

6
Matt Slagle

Wenn Sie die Abhängigkeitsstruktur über Projektreferenzen oder über Abhängigkeiten auf der Lösungsebene definiert haben, können Sie "Lokal kopieren" sicher deaktivieren. Ich würde sogar sagen, dass dies eine bewährte Methode ist, da Sie damit MSBuild 3.5 parallel ausführen können ( via/maxcpucount), ohne dass unterschiedliche Prozesse beim Kopieren von referenzierten Assemblys übereinander stolpern.

unsere "bewährte Methode" besteht darin, Lösungen mit vielen Projekten zu vermeiden. Wir haben ein Verzeichnis namens "Matrix" mit aktuellen Versionen von Baugruppen, und alle Referenzen stammen aus diesem Verzeichnis. Wenn Sie ein Projekt ändern und sagen "Jetzt ist die Änderung abgeschlossen", können Sie die Assembly in das Verzeichnis "Matrix" kopieren. Alle Projekte, die von dieser Assembly abhängen, haben also die aktuelle (= neueste) Version.

Wenn Sie nur wenige Projekte in Lösung haben, ist der Erstellungsprozess viel schneller.

Sie können den Schritt "Assembly in Matrixverzeichnis kopieren" mit Visual Studio-Makros oder mit "Menü -> Tools -> Externe Tools ..." automatisieren.

4
TcKs

Sie müssen die CopyLocal-Werte nicht ändern. Sie müssen lediglich ein gemeinsames $ (OutputPath) für alle Projekte in der Lösung vordefinieren und $ (UseCommonOutputDirectory) auf true einstellen. Siehe hierzu: http://blogs.msdn.com/b/kirillosenkov/archive/2015/04/04/benutzungs-auch-common-intermediate-and-output-directory-for-your-solution. aspx

3
Sheen

Set CopyLocal = false verringert die Erstellungszeit, kann jedoch während der Bereitstellung unterschiedliche Probleme verursachen.

Es gibt viele Szenarien, in denen Sie "Lokal kopieren" auf "True" setzen müssen, z. 

  • Top-Level-Projekte, 
  • Abhängigkeiten der zweiten Ebene, 
  • DLLs werden durch Reflektion aufgerufen

Die möglichen Probleme, die in SO-Fragen beschrieben werden
" Wann sollte copy-local auf true gesetzt werden und wann nicht? ",
" Fehlermeldung 'Ein oder mehrere der angeforderten Typen können nicht geladen werden. Rufen Sie die LoaderExceptions-Eigenschaft ab, um weitere Informationen zu erhalten.' "
und aaron-stainback 's antwort für diese Frage.

Meine Erfahrung mit dem Setzen von CopyLocal = false war NICHT erfolgreich. Siehe meinen Blogbeitrag "Verändern Sie NICHT" Projektverweise lokal kopieren auf "false", es sei denn, Sie verstehen Teilfolgen. "

Die Zeit zum Lösen der Probleme übergewichtet die Vorteile der Einstellung von copyLocal = false.

2

Dies ist vielleicht nicht die beste Praxis, aber so arbeite ich. 

Mir ist aufgefallen, dass Managed C++ alle Binärdateien in $ (SolutionDir)/'DebugOrRelease' ..__ ablegt. Also habe ich auch alle meine C # -Projekte dort abgelegt. Ich habe auch das "Copy Local" aller Verweise auf Projekte in der Lösung deaktiviert. Ich hatte eine merkliche Verbesserung der Bauzeit in meiner kleinen 10-Projektlösung. Diese Lösung ist eine Mischung aus C # -, verwalteten C++ -, nativen C++ -, C # Webservice- und Installationsprojekten. 

Vielleicht ist etwas kaputt, aber da dies die einzige Art ist, wie ich arbeite, merke ich es nicht.

Es wäre interessant herauszufinden, was ich zerbreche.

1
jyoung

Ich neige dazu, ein allgemeines Verzeichnis (z. B. ..\bin) zu erstellen, sodass ich kleine Testlösungen erstellen kann.

1
kenny

Sie können versuchen, einen Ordner zu verwenden, in den alle Assemblys kopiert werden, die von Projekten gemeinsam verwendet werden, und dann eine DEVPATH-Umgebungsvariable erstellen und festlegen 

<developmentMode developerInstallation="true" /> 

in der Datei machine.config auf der Arbeitsstation jedes Entwicklers. Sie müssen lediglich eine neue Version in den Ordner kopieren, in den die DEVPATH-Variable zeigt.

Falls möglich, teilen Sie Ihre Lösung in einige kleinere Lösungen auf.

1
Aleksandar

Wenn die Referenz nicht in der GAC enthalten ist, müssen Sie Copy Local auf true setzen, damit die Anwendung funktioniert. Wenn wir sicher sind, dass die Referenz in der GAC vorinstalliert ist, kann sie auf false gesetzt werden.

0
SharpGobi

Wenn Sie eine zentrale Stelle für den Verweis auf eine DLL mit der Kopie local false haben möchten, schlägt dies ohne die GAC fehl.

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

0
user306031

Ich weiß zwar nicht, wie die Probleme klappen, aber ich hatte Kontakt mit einer Build-Lösung, die dazu beitrug, dass alle erstellten Dateien mit Hilfe von symbolischen Links auf eine ramdisk gestellt wurden. . 

  • c:\Lösungsordner\bin -> Ramdisk r:\Lösungsordner\bin \
  • c:\solution ordner\obj -> ramdisk r:\solution ordner\obj \

  • Sie können dem Visual Studio auch mitteilen, welches temporäre Verzeichnis es für den Build verwenden kann. 

Eigentlich war das nicht alles, was es tat. Aber es hat wirklich mein Verständnis von Leistung getroffen. 
100% Prozessorauslastung und ein großes Projekt in weniger als 3 Minuten mit allen Abhängigkeiten. 

0
user7179930

Sie können Ihre Projektverweise auf die Debug-Versionen der dlls verweisen In Ihrem msbuild-Skript können Sie den /p:Configuration=Release setzen, sodass Sie eine Release-Version Ihrer Anwendung und aller Satellitenassemblys haben.

0
Bruno Shine

Normalerweise müssen Sie nur lokal kopieren, wenn Sie Ihr Projekt mit der in Ihrem Bin befindlichen DLL verwenden möchten, oder wo auch immer Sie sich befinden (GAC, andere Projekte usw.).

Ich stimme den anderen Leuten zu, dass Sie auch versuchen sollten, diese Lösung aufzubrechen, wenn dies möglich ist.

Sie können Configuration Manager auch verwenden, um verschiedene Build-Konfigurationen innerhalb dieser einen Lösung zu erstellen, die nur bestimmte Projektsätze erstellen.

Es wäre seltsam, wenn alle 100 Projekte aufeinander angewiesen wären. Daher sollten Sie entweder in der Lage sein, das Projekt zu trennen oder Configuration Manager zu verwenden, um sich selbst zu helfen.

0
CubanX