
Bei der Entwicklung von Plugins für CAD-Anwendungen ( sind das AutoCAD, Revit und Renga) taucht im Laufe der Zeit ein Problem auf – es erscheinen neue Versionen der Programme, ihre APIs ändern sich und es müssen neue Versionen der Plugins erstellt werden.
Wenn Sie nur ein Plugin haben oder noch ein neuer Selbstlerner in diesem Bereich sind, können Sie einfach eine Kopie des Projekts erstellen, die erforderlichen Stellen ändern und eine neue Version des Plugins erstellen. Folglich führt eine spätere Änderung des Codes zu einem erheblichen Anstieg des Arbeitsaufwands.
Mit steigender Erfahrung und Wissen werden Sie einige Möglichkeiten zur Automatisierung dieses Prozesses finden. Ich habe diesen Weg beschritten und möchte Ihnen erzählen, was ich letztendlich herausgefunden habe und wie praktisch das ist.
Zunächst betrachten wir die Methode, die offensichtlich ist und die ich lange Zeit verwendet habe.
Links zu den Projektdateien
Um es einfach, übersichtlich und verständlich zu halten, werde ich alles an einem abstrakten Beispiel der Plugin-Entwicklung beschreiben.
Öffnen Sie Visual Studio (ich benutze die Community-Version 2019. Und ja – auf Russisch) und erstellen Sie eine neue Lösung. Nennen wir sie MySuperPluginForRevit

Wir werden ein Plugin für Revit für die Versionen 2015-2020 entwickeln. Daher werde ich innerhalb der Lösung ein neues Projekt (Class Library .NET Framework) erstellen und es nennen MySuperPluginForRevit_2015

Wir müssen Referenzen auf die Revit API hinzufügen. Natürlich können wir Referenzen auf lokale Dateien hinzufügen (wir müssten alle erforderlichen SDKs oder alle Versionen von Revit installieren), aber wir gehen sofort den richtigen Weg und fügen das NuGet-Paket hinzu. Sie können eine große Anzahl von Paketen finden, aber ich werde meine eigenen verwenden.
Nachdem das Paket hinzugefügt wurde, klicken wir mit der rechten Maustaste auf den Punkt „Linksund wählen im Menü den Punkt „Packages.config in PackageReference verschieben…»

Falls bei Ihnen in diesem Moment Panik auftritt, da im Eigenschaftenfenster des Pakets der wichtige Punkt „Local Copynicht auf den Wert gesetzt werden kann, den wir dringend benötigen, nämlich false, dann brauchen Sie nicht in Panik zu geraten – gehen Sie zum Projektordner, öffnen Sie die Datei mit der Endung .csproj in einem Editor Ihrer Wahl (ich benutze Notepad++) und suchen Sie dort nach dem Eintrag zu unserem Paket. Es sieht jetzt so aus:
1.0.0Fügen Sie ihm die Eigenschaft hinzu runtime. Es wird so aussehen:
1.0.0
runtimeJetzt werden die Dateien aus dem Paket nicht mehr in den Ausgangsordner kopiert, wenn das Projekt erstellt wird.
Lassen Sie uns weitermachen – nehmen wir an, dass unser Plugin etwas aus der Revit API verwendet, welches sich mit der Zeit bei neuen Versionen geändert hat. Oder wir müssen einfach etwas im Code abhängig von der Revit-Version, für die wir das Plugin machen, ändern. Um solche Unterschiede im Code zu lösen, werden wir bedingte Kompilierungskennzeichen verwenden. Öffnen Sie die Projekteigenschaften und gehen Sie zum Tab „Bau„ und im Feld „Bedingte Kompilierungskennzeichen„ schreiben wir R2015.

Bitte beachten Sie, dass das Kennzeichen sowohl für die Debug- als auch für die Release-Konfiguration hinzugefügt werden muss.
Während wir uns im Eigenschaftenfenster befinden, wechseln wir sofort zum Tab „Anwendung„ und im Feld „Standard-Namespace„ und entfernen das Suffix _2015, sodass unser Namespace universell und unabhängig vom Namen der Assembly ist:

In meinem Fall werden die Plugins aller Versionen in einen Ordner gelegt, daher bleiben die Assembly-Namen mit einem Suffix vom Typ _20xx. Sie können das Suffix auch aus dem Namen der Assembly entfernen, wenn ein Speicherort der Dateien in verschiedenen Ordnern vorgesehen ist.
Kommen wir zum Code der Datei Class1.cs und simulieren einen Code unter Berücksichtigung verschiedener Revit-Versionen:
namespace MySuperPluginForRevit
{
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
[Regeneration(RegenerationOption.Manual)]
[Transaction(TransactionMode.Manual)]
public class Class1 : IExternalCommand
{
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
#if R2015
TaskDialog.Show("ModPlus", "Hallo Revit 2015");
#elif R2016
TaskDialog.Show("ModPlus", "Hallo Revit 2016");
#elif R2017
TaskDialog.Show("ModPlus", "Hallo Revit 2017");
#elif R2018
TaskDialog.Show("ModPlus", "Hallo Revit 2018");
#elif R2019
TaskDialog.Show("ModPlus", "Hallo Revit 2019");
#elif R2020
TaskDialog.Show("ModPlus", "Hallo Revit 2020");
#endif
return Result.Succeeded;
}
}
}Ich habe sofort alle Revit-Versionen über 2015 berücksichtigt (die zu dem Zeitpunkt des Verfassens des Artikels existierten) und die bedingten Kompilierungskennzeichen, die bei mir nach dem gleichen Muster erstellt werden.
Kommen wir zu dem Hauptpunkt. Wir erstellen ein neues Projekt in unserer Lösung, diesmal für die Plugin-Version unter Revit 2016. Wir wiederholen alle oben beschriebenen Schritte und ersetzen dabei die Zahl 2015 durch die Zahl 2016. Aber die Datei Class1.cs aus dem neuen Projekt löschen wir.

Die Datei mit dem benötigten Code – Class1.cs – haben wir bereits, und wir müssen nur den Link in das neue Projekt einfügen. Es gibt zwei Möglichkeiten, Links einzufügen:
- Langes – Klicken Sie mit der rechten Maustaste auf das Projekt und wählen Sie den Punkt „In dem sich öffnenden Fenster füllen Sie die folgenden Felder aus:„ -> „Vorhandenes Element„, im sich öffnenden Fenster finden wir die benötigte Datei und wählen anstelle der Option „In dem sich öffnenden Fenster füllen Sie die folgenden Felder aus:“ die Option „Als Verknüpfung hinzufügen»

- Kurz – direkt im Projektmappen-Explorer wählen wir die benötigte Datei (oder sogar Dateien. Sie können sogar ganze Ordner nehmen) und ziehen sie mit gedrückter Alt-Taste in das neue Projekt. Beim Ziehen sehen Sie, dass sich der Cursor beim Drücken der Alt-Taste von einem Pluszeichen in einen Pfeil ändert.
UPD: Ich habe ein wenig Verwirrung in diesen Absatz gebracht – um mehrere Dateien zu verschieben, müssen Sie die Shift+Alt!
gedrückt halten. Nach Durchführung des Verfahrens wird in unserem zweiten Projekt die Datei erscheinen, Class1.cs mit dem entsprechenden Symbol (blauer Pfeil):

Beim Bearbeiten des Codes im Editor-Fenster können Sie auch auswählen, in welchem Kontext das Projekt angezeigt wird, was Ihnen ermöglicht, den Code unter verschiedenen Bedingungen der bedingten Kompilierung zu sehen und zu bearbeiten:

Nach diesem Schema erstellen wir alle anderen Projekte (2017-2020). Lebenshack – wenn Sie Dateien im Projektmappen-Explorer nicht aus dem Basisprojekt ziehen, sondern aus einem Projekt, in dem sie bereits als Verknüpfung eingefügt sind, müssen Sie die Alt-Taste nicht gedrückt halten!
Die beschriebene Option ist bis zur Hinzufügung einer neuen Plugin-Version oder bis zur Hinzufügung neuer Dateien in das Projekt vollkommen in Ordnung – alles wird sehr mühsam. Und kürzlich habe ich plötzlich erkannt, wie man all dies mit einem einzigen Projekt lösen kann, und wir gehen zur zweiten Methode über.
Die Magie der Konfigurationen
Wenn Sie hier angekommen sind, könnten Sie ausrufen: „Warum hast du die erste Methode beschrieben, wenn der Artikel sofort über die zweite geht?!“. Ich habe das alles beschrieben, um klarer zu machen, wofür wir bedingte Kompilierungssymbole benötigen und an welchen Stellen sich unsere Projekte unterscheiden. Und jetzt wird uns klarer, welche Unterschiede wir umsetzen müssen, indem wir nur ein Projekt lassen.
Und damit alles offensichtlicher wird, werden wir kein neues Projekt erstellen, sondern Änderungen an unserem aktuellen Projekt vornehmen, das mit der ersten Methode erstellt wurde.
Zunächst entfernen wir aus der Lösung alle Projekte außer dem Hauptprojekt (das die Dateien enthält). Das heißt, die Projekte für die Versionen 2016-2020. Wir öffnen den Ordner mit der Lösung und löschen dort die Ordner dieser Projekte.
In unserer Lösung bleibt nur ein Projekt – MySuperPluginForRevit_2015. Wir öffnen seine Eigenschaften und:
- Auf der Registerkarte „Anwendung“ entfernen wir das Suffix aus dem Projektnamen _2015 (später wird klar, warum)
- Auf der Registerkarte „Bau» Entfernen Sie das Symbol für bedingte Kompilierung R2015 aus dem entsprechenden Feld
Hinweis: In der neuesten Version von Visual Studio gibt es einen Bug – Symbole für bedingte Kompilierung werden im Eigenschaftenfenster des Projekts nicht angezeigt, obwohl sie vorhanden sind. Wenn Sie diesen Bug feststellen, müssen Sie sie manuell aus der Datei .csproj entfernen. Wir müssen jedoch weiterhin damit arbeiten, also lesen wir weiter.
Benennen Sie das Projekt im Projektmappen-Explorer um, indem Sie das Suffix entfernen _2015 und entfernen Sie dann das Projekt aus der Lösung. Dies ist notwendig, um Ordnung und das Gefühl von Perfektionisten zu wahren! Öffnen Sie den Ordner unserer Lösung, benennen Sie den Projektordner entsprechend um und laden Sie das Projekt zurück in die Lösung.
Öffnen Sie den Konfiguration-Manager. Die Konfiguration Release benötigen wir eigentlich nicht, also löschen wir sie. Erstellen Sie neue Konfigurationen mit den uns bereits vertrauten Namen. R2015, R2016, …, R2020. Beachten Sie, dass Sie keine Parameter aus anderen Konfigurationen kopieren müssen und keine Projektkonfigurationen erstellen müssen:

Gehen Sie in den Projektordner und öffnen Sie die Datei mit der Erweiterung .csproj in einem Ihnen angenehmen Editor. Übrigens kann sie auch in Visual Studio geöffnet werden – Sie müssen das Projekt entladen und dann im Kontextmenü gibt es den benötigten Punkt:

In Visual Studio zu bearbeiten ist sogar bevorzugt, da der Editor sowohl formatieren als auch Hinweise geben kann.
In der Datei sehen wir die Elemente – ganz oben steht das allgemeine und danach folgen die mit Bedingungen. Diese Elemente definieren die Eigenschaften des Projekts während des Builds. Das erste Element, das ohne Bedingungen ist, legt die allgemeinen Eigenschaften fest, und die Elemente mit Bedingungen ändern entsprechend einige Eigenschaften je nach Konfiguration.
Gehen Sie zum allgemeinen (ersten) Element PropertyGroup und schauen Sie sich die Eigenschaft an AssemblyName – dies ist der Name des Builds und er sollte ohne Suffix sein _2015. Wenn ein Suffix vorhanden ist, entfernen Sie es.
Suchen Sie das Element mit der Bedingung
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">Es wird nicht benötigt – löschen Sie es.
Das Element mit der Bedingung
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">wird für die Arbeit in der Entwicklungs- und Debugging-Phase benötigt. Sie können seine Eigenschaften nach Ihren Bedürfnissen ändern – verschiedene Ausgabepfade festlegen, Symbole für bedingte Kompilierung ändern usw.
Jetzt erstellen wir neue Elemente PropertyGroup für unsere Konfigurationen. In diesen Elementen müssen wir nur vier Eigenschaften festlegen:
- OutputPath – der Ausgabepfad. Ich lege den Standardwert fest binR20xx
- DefineConstants – Compiler-Bedingungen. Ein Wert sollte festgelegt werden. TRACE;R20xx
- ZielFrameworkVersion – Die Plattformversion. Für verschiedene Versionen der Revit API müssen verschiedene Plattformen angegeben werden.
- AssemblyName – Der Name der Assembly (d.h. der Dateiname). Sie können den benötigten Assemblierungsnamen direkt eingeben, aber für die Universalität empfehle ich, den Wert zu schreiben. $(AssemblyName)_20xx. Dazu haben wir zuvor das Suffix aus dem Assemblierungsnamen entfernt.
Das Wichtigste an all diesen Elementen ist, dass sie ganz einfach in andere Projekte kopiert werden können, ohne sie zu ändern. Im Folgenden werde ich den gesamten Inhalt der .csproj-Datei beifügen.
Gut, wir haben die Projekteigenschaften geklärt – das ist nicht schwierig. Aber was ist mit den verwendeten Bibliotheken (NuGet-Paketen)? Wenn wir weitermachen, sehen wir, dass die verwendeten Bibliotheken in den Elementen angegeben werden. . Aber hier haben wir das Problem – dieses Element verarbeitet die Bedingungen falsch, ähnlich wie das Element. PropertyGroup. Das könnte sogar ein Fehler in Visual Studio sein, aber wenn Sie mehrere Elemente mit Konfigurationsbedingungen angeben und unterschiedliche Links zu NuGet-Paketen einfügen, dann werden bei der Änderung der Konfiguration alle angegebenen Pakete in das Projekt eingebunden. ItemGroup Hierbei kommt uns das Element
, das nach der uns vertrauten Logik funktioniert, zur Hilfe. Durch die Verwendung des Elements if-then-else.
, geben wir verschiedene NuGet-Pakete für verschiedene Konfigurationen an: Wählen SieDer gesamte Inhalt csproj
Der gesamte Inhalt der csproj
Debug
AnyCPU
{5AD738D6-4122-4E76-B865-BE7CE0F6B3EB}
Library
Properties
MySuperPluginForRevit
MySuperPluginForRevit
v4.5
512
true
true
full
false
binDebug
DEBUG;R2015
prompt
4
binR2015
TRACE;R2015
v4.5
$(AssemblyName)_2015
binR2016
TRACE;R2016
v4.5
$(AssemblyName)_2016
binR2017
TRACE;R2017
v4.5.2
$(AssemblyName)_2017
binR2018
TRACE;R2018
v4.5.2
$(AssemblyName)_2018
binR2019
TRACE;R2019
v4.7
$(AssemblyName)_2019
binR2020
TRACE;R2020
v4.7
$(AssemblyName)_2020
1.0.0
runtime
1.0.0
runtime
1.0.0
runtime
1.0.0
runtime
1.0.0
runtime
1.0.0
runtimeBitte beachten Sie, dass ich in einer der Bedingungen zwei Konfigurationen über ODER (Or). So wird das benötigte Paket bei der Konfiguration verbunden. Debug.
Und nun haben wir so gut wie alles perfekt. Wir laden das Projekt erneut, aktivieren die benötigte Konfiguration, rufen im Kontextmenü der Lösung (nicht des Projekts) den Punkt „Alle NuGet-Pakete wiederherstellen“ auf und sehen, wie sich unsere Pakete ändern.

An diesem Punkt bin ich jedoch auf ein Problem gestoßen – um alle Konfigurationen gleichzeitig zu bauen, könnten wir das Batch-Build verwenden (Menü „Bau„ -> „Batch-Build“), aber beim Wechseln der Konfigurationen erfolgt keine automatische Wiederherstellung der Pakete. Und auch beim Erstellen des Projekts passiert nichts, obwohl es eigentlich so sein sollte. Eine Lösung für dieses Problem mit den Standardmitteln habe ich nicht gefunden. Und es ist höchstwahrscheinlich auch ein Bug in Visual Studio.
Daher wurde entschieden, für das Batch-Build ein spezielles Automatisierungssystem zu verwenden . Eigentlich wollte ich das nicht, da ich es im Rahmen der Plugin-Entwicklung für überflüssig halte, aber momentan sehe ich keine andere Lösung. Und auf die Frage „Warum gerade Nuke?“ ist die Antwort einfach – wir verwenden es bei der Arbeit.
Also gehen wir in den Ordner unserer Lösung (nicht des Projekts), drücken die Taste Shift und klicken mit der rechten Maustaste an einen leeren Platz im Ordner – im Kontextmenü wählen wir den Punkt „PowerShell-Fenster hier öffnen».

Wenn Sie noch nicht installiert haben nuke, geben Sie zunächst den Befehl ein
dotnet tool install Nuke.GlobalTool –globalJetzt geben Sie den Befehl ein nuke und Sie werden aufgefordert, nuke für das aktuelle Projekt zu konfigurieren. Ich weiß nicht, wie man das korrekt auf Englisch schreibt – auf Englisch würde geschrieben werden: Could not find .nuke file. Do you want to setup a build? [y/n]
Wir drücken die Taste Y und dann folgen die nächsten Einrichtungspunkte. Wir benötigen die einfachste Variante mit Verwendung von MSBuild, daher antworten wir wie im Screenshot:

Gehen wir zu Visual Studio, die uns anbietet, die Lösung neu zu laden, da ein neues Projekt hinzugefügt wurde. Wir laden die Lösung neu und sehen, dass wir ein Projekt erhalten haben build in dem uns nur eine Datei interessiert – Build.cs

Öffnen Sie diese Datei und schreiben Sie ein Skript zum Erstellen des Projekts für alle Konfigurationen. Oder verwenden Sie mein Skript, das Sie anpassen können:
using System.IO;
using Nuke.Common;
using Nuke.Common.Execution;
using Nuke.Common.ProjectModel;
using Nuke.Common.Tools.MSBuild;
using static Nuke.Common.Tools.MSBuild.MSBuildTasks;
[CheckBuildProjectConfigurations]
[UnsetVisualStudioEnvironmentVariables]
class Build : NukeBuild
{
public static int Main () => Execute(x => x.Compile);
[Solution] readonly Solution Solution;
// Wenn der Lösungsname und der Projekt (Plugin) Name unterschiedlich sind, geben Sie hier den Projektnamen (Plugin) an
string PluginName => Solution.Name;
Target Compile => _ => _
.Executes(() =>
{
var project = Solution.GetProject(PluginName);
if (project == null)
throw new FileNotFoundException("Nicht gefunden!");
var build = new List();
foreach (var (_, c) in project.Configurations)
{
var configuration = c.Split("|")[0];
if (configuration == "Debug" || build.Contains(configuration))
continue;
Logger.Normal($"Konfiguration: {configuration}");
build.Add(configuration);
MSBuild(_ => _
.SetProjectFile(project.Path)
.SetConfiguration(configuration)
.SetTargets("Restore"));
MSBuild(_ => _
.SetProjectFile(project.Path)
.SetConfiguration(configuration)
.SetTargets("Rebuild"));
}
});
}Wir kehren zum PowerShell-Fenster zurück und geben erneut den Befehl ein nuke (es ist möglich, den Befehl einzugeben nuke mit der Angabe des gewünschten Ziels. Aber wir haben einen Ziels, der standardmäßig gestartet wird). Nach dem Drücken der Enter-Taste werden wir uns wie echte Hacker fühlen, da die automatische Erstellung unseres Projekts für verschiedene Konfigurationen stattfinden wird, wie im Film.
Übrigens kann man PowerShell direkt aus Visual Studio verwenden (Menü „Ansicht„ -> „Andere Fenster„ -> „Paket-Manager-Konsole“), aber dort wird alles schwarz-weiß sein, was nicht sehr bequem ist.
Damit ist mein Artikel beendet. Ich bin sicher, dass Sie mit der Option für AutoCAD selbst klarkommen werden. Ich hoffe, dass das hier dargestellte Material seine "Kunden" finden wird.
Vielen Dank für Ihre Aufmerksamkeit!
Quelle: habr.com
