
Durante lo sviluppo di plugin per applicazioni CAD ( sono AutoCAD, Revit e Renga), si presenta col tempo un problema: vengono rilasciate nuove versioni dei programmi, cambia la loro API e bisogna creare nuove versioni dei plugin.
Quando hai solo un plugin o sei ancora un principiante autodidatta in questo campo, puoi semplicemente copiare il progetto, modificare le parti necessarie e compilare una nuova versione del plugin. Di conseguenza, l'apporto successivo di modifiche al codice porterà a un aumento esponenziale dello sforzo lavorativo.
Con l'accumularsi di esperienza e conoscenze, troverai diversi modi per automatizzare questo processo. Ho percorso questa strada e voglio raccontarti a cosa sono arrivato alla fine e quanto sia comodo.
Per iniziare, consideriamo un metodo che è ovvio e di cui ho fatto uso per molto tempo
Riferimenti ai file del progetto
E per rendere tutto semplice, chiaro e comprensibile, descriverò tutto con un esempio astratto di sviluppo di un plugin.
Apriamo Visual Studio (ho la versione Community 2019. E sì – in lingua russa) e creiamo una nuova soluzione. La chiameremo MySuperPluginForRevit

Stiamo creando un plugin per Revit per le versioni 2015-2020. Pertanto, creerò un nuovo progetto nella soluzione (Libreria di classi .Net Framework) e lo chiamerò MySuperPluginForRevit_2015

Dobbiamo aggiungere riferimenti all'API di Revit. Certo, possiamo aggiungere riferimenti a file locali (dovremo installare tutti i SDK necessari o tutte le versioni di Revit), ma noi seguiremo subito il percorso giusto e collegheremo il pacchetto NuGet. Puoi trovare un numero considerevole di pacchetti, ma io utilizzerò i miei.
Dopo aver collegato il pacchetto, facciamo clic con il tasto destro sul punto «Link» e selezioniamo nel menu l'opzione «Spostare packages.config in PackageReference…»

Se ad un certo punto ti viene la panico, visto che nella finestra delle proprietà del pacchetto non ci sarà l'importante opzione «Copiare localmente», che dobbiamo assolutamente impostare su false, non c'è bisogno di panico: andiamo nella cartella del progetto, apriamo il file con estensione .csproj in un editor a te comodo (io uso Notepad++) e troviamo la voce riguardante il nostro pacchetto. Ora appare così:
1.0.0Aggiungiamo la sua proprietà runtime. Risulterà così:
1.0.0
runtimeOra, quando si costruisce un progetto, i file del pacchetto non verranno copiati nella cartella di output.
Andiamo avanti: immaginiamo subito che il nostro plugin utilizzerà qualcosa dall'API Revit, che è cambiato nel tempo con le nuove versioni uscite. Oppure semplicemente dobbiamo modificare qualcosa nel codice a seconda della versione di Revit per la quale stiamo creando il plugin. Per affrontare tali differenze nel codice, utilizzeremo i simboli di compilazione condizionale. Apriamo le proprietà del progetto, andiamo alla scheda "Compilazione" e nel campo "Simboli di compilazione condizionale" scriveremo R2015.

Si prega di notare che il simbolo deve essere aggiunto sia per la configurazione di Debug che per la configurazione di Release.
E mentre siamo nella finestra delle proprietà, passiamo subito alla scheda "Applicazione" e nel campo "Spazio dei nomi predefinito" rimuoviamo il suffisso _2015, in modo che abbiamo uno spazio dei nomi universale e indipendente dal nome dell'assemblaggio:

Nel mio caso, nel prodotto finale, i plugin di tutte le versioni si trovano in una sola cartella, quindi i nomi degli assemblaggi rimangono con suffissi del tipo _20xx. Ma puoi rimuovere il suffisso anche dal nome dell'assemblaggio, se prevedi che i file si trovino in diverse cartelle.
Passiamo al codice del file Class1.cs e simuleremo un codice che tenga conto delle diverse versioni di Revit:
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", "Ciao Revit 2015");
#elif R2016
TaskDialog.Show("ModPlus", "Ciao Revit 2016");
#elif R2017
TaskDialog.Show("ModPlus", "Ciao Revit 2017");
#elif R2018
TaskDialog.Show("ModPlus", "Ciao Revit 2018");
#elif R2019
TaskDialog.Show("ModPlus", "Ciao Revit 2019");
#elif R2020
TaskDialog.Show("ModPlus", "Ciao Revit 2020");
#endif
return Result.Succeeded;
}
}
}Ho subito considerato tutte le versioni di Revit superiori alla versione 2015 (che erano disponibili al momento della scrittura dell'articolo) e ho incluso immediatamente i simboli di compilazione condizionale, che vengono creati secondo lo stesso schema.
Passiamo al punto principale. Creiamo un nuovo progetto nella nostra soluzione, questa volta per la versione del plugin per Revit 2016. Ripetiamo tutte le azioni descritte sopra, sostituendo il numero 2015 con il numero 2016. Ma il file Class1.cs del nuovo progetto lo eliminiamo.

Il file con il codice necessario – Class1.cs – è già disponibile e dobbiamo solo inserire un collegamento ad esso nel nuovo progetto. Ci sono due modi per inserire i collegamenti:
- Lungo – facciamo clic destro sul progetto, scegliamo l'opzione «Aggiungere» -> «Elemento esistente», nella finestra aperta troviamo il file necessario e invece dell'opzione «Aggiungere» scegliamo l'opzione «Aggiungere come collegamento»

- Breve – direttamente nell'esploratore soluzioni scegliamo il file necessario (o anche più file. È possibile anche intere cartelle) e trasciniamo nel nuovo progetto tenendo premuto il tasto Alt. Durante il trascinamento vedrete che premendo il tasto Alt il cursore del mouse cambierà da un segno più a una freccia.
UPD: Ho introdotto un po' di confusione in questo paragrafo: per spostare più file è necessario tenere premuto Shift+Alt!
Dopo aver eseguito la procedura avremo nel secondo progetto un file Class1.cs con l'icona corrispondente (freccia blu):

Durante la modifica del codice nella finestra dell'editor puoi anche scegliere in quale contesto visualizzare il codice, il che ti permetterà di vedere e modificare il codice con diversi simboli di compilazione condizionale:

Secondo questo schema creiamo tutti gli altri progetti (2017-2020). Suggerimento: se trascini i file nell'esploratore soluzioni da un progetto che non è quello di base, ma da un progetto in cui sono già stati inseriti come collegamenti, non è necessario tenere premuto il tasto Alt!
L'alternativa descritta è piuttosto buona fino al momento in cui aggiungi una nuova versione del plugin o nuovi file nel progetto - tutto ciò diventa molto laborioso. E recentemente mi sono accorto di come risolvere tutto questo con un solo progetto e passiamo al secondo metodo
Magia delle configurazioni
Arrivato a questo punto, potresti esclamare «E perché hai descritto il primo metodo, se l'articolo parla subito del secondo?!». L'ho descritto per chiarire a cosa ci servono i simboli di compilazione condizionale e in quali punti i nostri progetti si differenziano. E ora ci è più chiaro quali differenze specifiche dei progetti dobbiamo implementare, lasciando solo un progetto.
E per rendere tutto più chiaro, non creeremo un nuovo progetto, ma apporteremo modifiche al nostro progetto attuale, creato con il primo metodo.
Quindi, prima di tutto, rimuoviamo dalla soluzione tutti i progetti, tranne quello principale (che contiene direttamente i file). Cioè, i progetti per le versioni 2016-2020. Apriamo la cartella con la soluzione e rimuoviamo lì le cartelle di questi progetti.
Nella nostra soluzione è rimasto un solo progetto — MySuperPluginForRevit_2015. Apriamo le sue proprietà e:
- Nella scheda «Applicazione» dal nome della build rimuoviamo il suffisso _2015 (poi diventerà chiaro il perché)
- Nella scheda «Compilazione» rimuoviamo il simbolo di compilazione condizionale R2015 dal campo corrispondente
Nota: nell'ultima versione di Visual Studio c'è un bug: i simboli di compilazione condizionale non vengono visualizzati nella finestra delle proprietà del progetto, anche se sono presenti. Se notate questo bug, dovete rimuoverli manualmente dal file .csproj. Tuttavia, dobbiamo comunque lavorarci, quindi continuiamo a leggere.
Rinominiamo il progetto nella finestra dell'Esplora soluzioni, rimuovendo il suffisso _2015 e poi rimuoviamo il progetto dalla soluzione. È necessario per mantenere ordine e sensibilità tra i perfezionisti! Apriamo la cartella della nostra soluzione, rinominiamo lì la cartella del progetto allo stesso modo e ricarichiamo il progetto nella soluzione.
Apriamo il gestore delle configurazioni. La configurazione Release in linea di principio non ci servirà, quindi la rimuoviamo. Creiamo nuove configurazioni con nomi già familiari R2015, R2016, …, R2020. Si prega di notare che non è necessario copiare i parametri da altre configurazioni e non è necessario creare configurazioni del progetto:

Andiamo nella cartella del progetto e apriamo il file con estensione .csproj in un editor di vostra scelta. Tra l'altro, può essere aperto anche in Visual Studio: bisogna scaricare il progetto e poi nel menu contestuale ci sarà l'opzione necessaria:

Modificare in Visual Studio è addirittura preferibile, poiché l'editor allinea e fornisce suggerimenti.
Nel file vedremo gli elementi – in alto ci sono le proprietà comuni e in seguito ci sono quelle con condizioni. Questi elementi impostano le proprietà del progetto durante la sua compilazione. Il primo elemento, senza condizioni, imposta le proprietà generali, mentre gli elementi con condizioni modificano alcune proprietà in base alle configurazioni.
Passiamo all'elemento generale (primo) PropertyGroup e guardiamo la proprietà AssemblyName – questo è il nome dell'assembly e dovrebbe essere senza suffisso _2015. Se è presente un suffisso, lo rimuoviamo.
Troviamo l'elemento con la condizione
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">Non ci serve – lo rimuoviamo.
L'elemento con la condizione
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">sarà necessario per lavorare durante la fase di sviluppo e debug del codice. Potete modificare le sue proprietà secondo le vostre necessità: impostare percorsi di output diversi, modificare i simboli di compilazione condizionale e così via.
Ora creiamo nuovi elementi PropertyGroup per le nostre configurazioni. In questi elementi dovremo impostare solo quattro proprietà:
- OutputPath – cartella di output. Imposto il valore standard binR20xx
- DefineConstants – simboli di compilazione condizionale. Dovrebbe essere specificato un valore TRACE;R20xx
- TargetFrameworkVersion – versione della piattaforma. È necessario specificare piattaforme diverse per le diverse versioni dell'API di Revit.
- AssemblyName – nome dell'assembly (cioè il nome del file). Puoi scrivere direttamente il nome dell'assembly desiderato, ma per universalità ti consiglio di scrivere il valore $(AssemblyName)_20xx. Per questo abbiamo precedentemente rimosso il suffisso dal nome dell'assembly
La caratteristica principale di tutti questi elementi è che possono essere semplicemente copiati in altri progetti senza alcuna modifica. Più avanti nell'articolo allegherò tutto il contenuto del file .csproj.
Bene, abbiamo chiarito le proprietà del progetto – non è difficile. Ma cosa fare con le librerie collegate (pacchetti NuGet). Se guardiamo oltre, vedremo che le librerie collegate vengono definite negli elementi . Ma ecco il problema – questo elemento gestisce in modo errato le condizioni, come l'elemento PropertyGroup. Forse è anche un bug di Visual Studio, ma se si specificano più elementi ItemGroup con condizioni di configurazione e si inseriscono link diversi ai pacchetti NuGet, allora al cambio di configurazione vengono collegate al progetto tutti i pacchetti specificati.
In nostro aiuto viene l'elemento , che funziona secondo la logica a noi familiare if-then-else.
Utilizzando l'elemento Choose, specifichiamo diversi pacchetti NuGet per diverse configurazioni:
Tutto il contenuto 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
runtimeSi prega di notare che in una delle condizioni ho indicato due configurazioni tramite OPPURE (Or). In questo modo si collegherà il pacchetto corretto per la configurazione Debug.
. Ecco, quasi tutto è perfetto. Ricarichiamo il progetto, attiviamo la configurazione che ci interessa, facciamo clic sul menu contestuale della soluzione (non del progetto) sull'opzione «Ripristina tutti i pacchetti NuGet» e vediamo come cambiano i pacchetti.

E a questo punto sono arrivato a un vicolo cieco: per costruire tutte le configurazioni avremmo potuto utilizzare la build a pacchetto (menu «Compilazione» -> «Build a pacchetto»), ma al passaggio tra le configurazioni non avviene il ripristino automatico dei pacchetti. E nemmeno durante la costruzione del progetto, sebbene, in teoria, dovrebbe. Non ho trovato soluzione a questo problema con i metodi standard. E probabilmente è anche un bug di Visual Studio.
Quindi, per la build a pacchetto si è deciso di utilizzare un sistema di automazione della build . In realtà non volevo farlo, poiché lo considero superfluo nell'ambito dello sviluppo di plugin, ma al momento non vedo un'altra soluzione. E alla domanda «Perché proprio Nuke?» la risposta è semplice: lo utilizziamo al lavoro.
Quindi, andiamo nella cartella della nostra soluzione (non del progetto), teniamo premuto il tasto Shift e facciamo clic con il pulsante destro del mouse su uno spazio vuoto nella cartella - nel menu contestuale scegliamo l'opzione «Apri finestra PowerShell qui».

Se non hai installato nuke, prima digita il comando
dotnet tool install Nuke.GlobalTool –globalOra digita il comando nuke e ti verrà chiesto di configurare nuke per il progetto attuale. Non so come scriverlo in modo più corretto in russo - in inglese sarà scritto Could not find .nuke file. Do you want to setup a build? [y/n]
Premiamo il tasto Y e poi ci saranno informazioni immediate sui punti di configurazione. Abbiamo bisogno della soluzione più semplice che utilizza MSBuild, quindi rispondiamo come nello screenshot:

Passiamo a Visual Studio, che ci propone di ricaricare la soluzione, poiché è stato aggiunto un nuovo progetto. Ricarichiamo la soluzione e vediamo che abbiamo un progetto build nel quale ci interessa solo un file - Build.cs

Apriamo questo file e scriviamo uno script per la costruzione del progetto per tutte le configurazioni. Oppure utilizziamo il mio script, che puoi modificare secondo le tue esigenze:
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;
// Se il nome della soluzione e il nome del progetto (plugin) sono diversi, indica qui il nome del progetto (plugin)
string PluginName => Solution.Name;
Target Compile => _ => _
.Executes(() =>
{
var project = Solution.GetProject(PluginName);
if (project == null)
throw new FileNotFoundException("Non trovato!");
var build = new List();
foreach (var (_, c) in project.Configurations)
{
var configuration = c.Split("|")[0];
if (configuration == "Debug" || build.Contains(configuration))
continue;
Logger.Normal($"Configurazione: {configuration}");
build.Add(configuration);
MSBuild(_ => _
.SetProjectFile(project.Path)
.SetConfiguration(configuration)
.SetTargets("Restore"));
MSBuild(_ => _
.SetProjectFile(project.Path)
.SetConfiguration(configuration)
.SetTargets("Rebuild"));
}
});
}Torniamo alla finestra di PowerShell e riscriviamo il comando nuke (puoi scrivere il comando nuke indicando quello necessario Target. Ma abbiamo uno Target, che viene eseguito di default). Dopo aver premuto il tasto Invio, ci sentiremo dei veri hacker, perché come nei film, avverrà la costruzione automatica del nostro progetto per diverse configurazioni.
A proposito, puoi utilizzare PowerShell direttamente da Visual Studio (menù «Vista» -> «Altre finestre» -> «Console del gestore pacchetti»), ma lì tutto sarà in bianco e nero, il che non è molto comodo.
Qui termina il mio articolo. Sono sicuro che con l'opzione per AutoCAD riuscirai a cavartela da solo. Spero che il materiale qui esposto trovi i suoi «clienti».
Grazie per l'attenzione!
Fonte: habr.com
