Option --output auf Projektmappenebene ist für buildbezogene Befehle nicht mehr gültig

Aufgrund einer Änderung im 7.0.200 SDK wird die --output/-o-Option bei Verwendung einer Projektmappendatei mit den folgenden Befehlen nicht mehr akzeptiert:

  • build
  • clean
  • pack
  • publish
  • store
  • test
  • vstest

Dies liegt daran, dass die Semantik der OutputPath-Eigenschaft, die von der --output/-o-Option gesteuert wird, für Projektmappen nicht gut definiert ist. Die Ausgabe auf diese Weise erstellter Projekte wird im selben Verzeichnis abgelegt, was inkonsistent ist und dazu geführt hat, dass Benutzer eine Reihe von Problemen gemeldet haben.

Diese Änderung wurde im 7.0.201 SDK auf den Schweregrad der Warnung reduziert, und pack wurde aus der Liste der betroffenen Befehle entfernt.

Eingeführt in Version

.NET 7.0.200 SDK, nur im SDK 7.0.201 auf eine Warnung reduziert.

Vorheriges Verhalten

Wenn Sie zuvor bei Verwendung einer Projektmappendatei --output/-o angegeben haben, wurde die Ausgabe für alle erstellten Projekte in einer nicht definierten und inkonsistenten Reihenfolge im angegebenen Verzeichnis platziert.

Neues Verhalten

Die dotnet CLI gibt einen Fehler aus, wenn die --output/-o-Option mit einer Projektmappendatei verwendet wird. Ab dem 7.0.201 SDK wird stattdessen eine Warnung ausgegeben, und im Fall von dotnet pack wird weder eine Warnung noch ein Fehler generiert.

Typ des Breaking Changes

Dieser Breaking Change erfordert möglicherweise Änderungen an Buildskripts und Continuous Integration-Pipelines. Dies wirkt sich dies sowohl auf die Binär- als auch auf die Quellkompatibilität aus.

Grund für die Änderung

Diese Änderung wurde vorgenommen, weil die Semantik der OutputPath-Eigenschaft, die von der --output/-o-Option gesteuert wird, für Projektmappen nicht gut definiert ist. Die Ausgabe auf diese Weise erstellter Projekte wird im selben Verzeichnis abgelegt, was inkonsistent ist und dazu geführt hat, dass Benutzer eine Reihe von Problemen gemeldet haben.

Wenn eine Projektmappe mit der --output-Option erstellt wird, wird die OutputPath-Eigenschaft für alle Projekte auf denselben Wert festgelegt, was bedeutet, dass die Ausgabe für alle Projekte im selben Verzeichnis abgelegt wird. Je nach Komplexität der Projekte in der Projektmappe können unterschiedliche und inkonsistente Ergebnisse auftreten. Sehen wir uns einige Beispiele verschiedener Projektmappenformen an, und wie sie von einem freigegebenen OutputPath beeinflusst werden.

Einzelnes Projekt, einzelnes TargetFramework

Stellen Sie sich eine Projektmappe vor, die ein einzelnes Projekt enthält, das auf ein einzelnes TargetFramework ausgerichtet ist, net7.0. In diesem Fall entspricht die Angabe der --output-Option dem Festlegen der OutputPath-Eigenschaft in der Projektdatei. Während eines Builds (oder anderer Befehle, aber wir beschränken die Erörterung jetzt auf den Build) werden alle Ausgaben für das Projekt im angegebenen Verzeichnis abgelegt.

Einzelnes Projekt, mehrere TargetFrameworks

Stellen Sie sich nun eine Projektmappe vor, die ein einzelnes Projekt mit mehreren TargetFrameworks enthält, net6.0 und net7.0. Aufgrund mehrerer Ziele wird das Projekt zweimal erstellt, einmal für jedes TargetFramework. Für jeden dieser „inneren“ Builds wird der OutputPath auf denselben Wert festgelegt, sodass die Ausgaben für jeden der inneren Builds im selben Verzeichnis platziert werden. Dies bedeutet, dass der letzte Build die Ausgaben des anderen Builds überschreibt, und in einem Parallelbuildsystem, in dem MSBuild standardmäßig ausgeführt wird, ist der „Letzte“ unbestimmt.

Bibliothek => Konsole => Test, einzelnes TargetFramework

Stellen Sie sich nun eine Projektmappe vor, die ein Bibliotheksprojekt enthält, ein Konsolenprojekt, das auf das Bibliotheksprojekt verweist, und ein Testprojekt, das auf das Konsolenprojekt verweist. Alle diese Projekte zielen auf ein einzelnes TargetFramework, net7.0. In diesem Fall wird zuerst das Bibliotheksprojekt und dann das Konsolenprojekt erstellt. Das Testprojekt wird zuletzt erstellt und verweist auf das Konsolenprojekt. Für jedes erstellte Projekt werden die Ausgaben der einzelnen Builds in das von OutputPath angegebene Verzeichnis kopiert, und so enthält das endgültige Verzeichnis Ressourcen aus allen drei Projekten. Dies funktioniert für Tests, aber für die Veröffentlichung kann es dazu führen, dass Testressourcen an die Produktion gesendet werden.

Bibliothek => Konsole => Test, mehrere TargetFrameworks

Verwenden Sie nun dieselbe Kette von Projekten, und fügen Sie ihnen zusätzlich zum net7.0-Build einen net6.0TargetFramework-Build hinzu. Dasselbe Problem wie bei dem Einzelprojektbuild für mehrere Ziele tritt auf: inkonsistentes Kopieren von TFM-spezifischen Ressourcen in das angegebene Verzeichnis.

Mehrere Apps

Bisher haben wir Szenarien mit einem linearen Abhängigkeitsdiagramm betrachtet, aber viele Projektmappen können mehrere verwandte Anwendungen enthalten. Dies bedeutet, dass mehrere Apps gleichzeitig für denselben Ausgabeordner erstellt werden können. Wenn die Apps eine Abhängigkeitsdatei mit demselben Namen enthalten, tritt beim Build möglicherweise zeitweilig ein Fehler auf, wenn mehrere Projekte gleichzeitig versuchen, in diese Datei im Ausgabepfad zu schreiben.

Wenn mehrere Apps von verschiedenen Versionen einer Datei abhängen, könnte es selbst bei erfolgreichem Build nicht eindeutig sein, welche Version der Datei in den Ausgabepfad kopiert wird. Dies kann vorkommen, wenn die Projekte (möglicherweise transitiv) von verschiedenen Versionen eines NuGet-Pakets abhängen. Innerhalb eines einzelnen Projekts trägt NuGet dazu bei, dass seine Abhängigkeiten (einschließlich aller transitiven Abhängigkeiten über NuGet-Pakete und/oder Projektverweise) zur selben Version vereinheitlicht sind. Da die Vereinheitlichung im Kontext eines einzelnen Projekts und seiner abhängigen Projekte erfolgt, ist es möglich, verschiedene Versionen eines Pakets aufzulösen, wenn zwei separate Projekte der obersten Ebene erstellt werden. Wenn das Projekt, das von der höheren Version abhängt, die Abhängigkeit zuletzt kopiert, werden die Apps häufig erfolgreich ausgeführt. Wenn die niedrigere Version jedoch zuletzt kopiert wird, kann die App, die für die höhere Version kompiliert wurde, die Assembly zur Runtime nicht laden. Da es nicht eindeutig sein könnte, welche Version kopiert wird, kann dies zu sporadischen, unzuverlässigen Builds führen, bei denen es sehr schwierig ist, das Problem zu diagnostizieren.

Weitere Beispiele

Weitere Beispiele dafür, wie sich dieser zugrunde liegende Fehler in der Praxis darstellt, finden Sie in der Erörterung zu dotnet/sdk#15607.

Allgemein sollten Sie die Aktion ausführen, die Sie zuvor ohne die --output/-o-Option ausgeführt haben, und dann die Ausgabe an die gewünschte Position verschieben, nachdem der Befehl abgeschlossen wurde. Es ist auch möglich, die Aktion für ein bestimmtes Projekt auszuführen und die --output/-o-Option trotzdem anzuwenden, da diese über eine klar definierte Semantik verfügt.

Wenn Sie das vorhandene Verhalten genau beibehalten möchten, können Sie das --property-Flag verwenden, um eine MSBuild-Eigenschaft auf das gewünschte Verzeichnis festzulegen. Die zu verwendende Eigenschaft variiert je nach Befehl:

Befehl Eigenschaft Beispiel
build OutputPath dotnet build --property:OutputPath=DESIRED_PATH
clean OutputPath dotnet clean --property:OutputPath=DESIRED_PATH
pack PackageOutputPath dotnet pack --property:PackageOutputPath=DESIRED_PATH
publish PublishDir dotnet publish --property:PublishDir=DESIRED_PATH
store OutputPath dotnet store --property:OutputPath=DESIRED_PATH
test TestResultsDirectory dotnet test --property:OutputPath=DESIRED_PATH

HINWEIS Um optimale Ergebnisse zu erzielen, sollte der DESIRED_PATH ein absoluter Pfad sein. Relative Pfade werden auf eine Weise „verankert“ (d. h. absolut gemacht), die Sie möglicherweise nicht erwarten, und funktionieren möglicherweise nicht mit allen Befehlen gleich.