Facem un proiect de plugin cu compilare pentru diferite versiuni Revit/AutoCAD

Facem un proiect de plugin cu compilare pentru diferite versiuni Revit/AutoCAD

Atunci când dezvolți plugin-uri pentru aplicațiile CAD (în cazul meu este AutoCAD, Revit și Renga), cu timpul apare o problemă – noi versiuni ale programelor sunt lansate, API-ul lor se schimbă și trebuie create versiuni noi ale plugin-urilor.

Când ai un singur plugin sau ești încă novice în acest domeniu, poți pur și simplu să faci o copie a proiectului, să schimbi locurile necesare și să compilezi o nouă versiune a plugin-ului. Ca urmare, modificările ulterioare în cod vor necesita un efort de muncă semnificativ crescut.

Pe măsură ce acumulezi experiență și cunoștințe, vei găsi câteva metode de automatizare a acestui proces. Eu am parcurs acest drum și vreau să îți povestesc despre ce am ajuns să realizez și cât de convenabil este.

Mai întâi, să luăm în considerare o metodă care este evidentă și pe care am folosit-o mult timp.

Referințe la fișierele de proiect.

Și pentru a face totul simplu, vizibil și ușor de înțeles, voi descrie totul printr-un exemplu abstract de dezvoltare a unui plugin.

Vom deschide Visual Studio (am versiunea Community 2019. Și da – în limba română) și vom crea o nouă soluție. O vom numi MySuperPluginForRevit.

Facem un proiect de plugin cu compilare pentru diferite versiuni Revit/AutoCAD

Vom crea un plugin pentru Revit pentru versiunile 2015-2020. Așadar, voi crea în soluție un nou proiect (Bibliotecă de clase Net Framework) și îl vom numi MySuperPluginForRevit_2015.

Facem un proiect de plugin cu compilare pentru diferite versiuni Revit/AutoCAD

Trebuie să adăugăm referințe la API-ul Revit. Desigur, putem adăuga referințe la fișierele locale (va trebui să ne instalăm toate SDK-urile necesare sau toate versiunile Revit), dar noi vom merge direct pe calea corectă și vom conecta pachetul NuGet. Poți găsi un număr considerabil de pachete, dar eu voi folosi propriile mele.

După ce am conectat pachetul, dăm clic dreapta pe elementul „Linkuri” și selectăm din meniu opțiunea „Mută packages.config în PackageReference…»

Facem un proiect de plugin cu compilare pentru diferite versiuni Revit/AutoCAD

Dacă cumva în acest moment începe panica, deoarece în fereastra de proprietăți a pachetului nu va exista un punct important „Copiere locală”, pe care trebuie să-l setăm la valoarea false, nu trebuie să te panichezi – mergem în folderul cu proiectul, deschidem fișierul cu extensia .csproj într-un editor care îți este convenabil (eu folosesc Notepad++) și căutăm în acel fișier înregistrarea despre pachetul nostru. Arată acum așa:

1.0.0

Îi adăugăm proprietatea runtime. Va arăta astfel:

1.0.0
  runtime

Acum, la crearea proiectului, fișierele din pachet nu vor fi copiate în folderul de ieșire.
Să mergem mai departe – să presupunem că pluginul nostru va folosi ceva din Revit API, care s-a schimbat de-a lungul timpului cu lansarea unor noi versiuni. Sau pur și simplu trebuie să schimbăm ceva în cod în funcție de versiunea Revit pentru care dezvoltăm pluginul. Pentru a gestiona aceste diferențe în cod, vom folosi simbolurile de compilare condiționată. Vom deschide proprietățile proiectului, vom trece la tab-ul "Compilare" și în câmpul "Simboluri de compilare condiționată" vom scrie R2015.

Facem un proiect de plugin cu compilare pentru diferite versiuni Revit/AutoCAD

Rețineți că simbolul trebuie adăugat atât pentru configurația Debug, cât și pentru configurația Release.

Și, cât ne aflăm în fereastra de proprietăți, trecem imediat la tab-ul "Aplicație" și în câmpul "Spațiu de nume implicit" și eliminăm sufixul _2015, astfel încât spațiul de nume să fie universal și independent de numele compilării:

Facem un proiect de plugin cu compilare pentru diferite versiuni Revit/AutoCAD

În cazul meu, în produsul final, pluginurile pentru toate versiunile se adună într-un singur folder, așa că numele compilărilor rămân cu sufixul de tip _20xx. Dar puteți elimina sufixul și din numele compilării dacă se preconizează că fișierele vor fi plasate în foldere diferite.

Trecem la codul fișierului Class1.cs și simulăm acolo un cod având în vedere diferitele versiuni 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", "Salut Revit 2015");
#elif R2016
            TaskDialog.Show("ModPlus", "Salut Revit 2016");
#elif R2017
            TaskDialog.Show("ModPlus", "Salut Revit 2017");
#elif R2018
            TaskDialog.Show("ModPlus", "Salut Revit 2018");
#elif R2019
            TaskDialog.Show("ModPlus", "Salut Revit 2019");
#elif R2020
            TaskDialog.Show("ModPlus", "Salut Revit 2020");
#endif
            return Result.Succeeded;
        }
    }
}

Am luat în considerare toate versiunile Revit de peste 2015 (care erau disponibile la momentul scrierii acestui articol) și am luat în considerare existența simbolurilor de compilare condiționată, care sunt create după același șablon.

Trecem la miezul problemei. Creăm un nou proiect în soluția noastră, dar deja pentru versiunea pluginului pentru Revit 2016. Repetăm toate acțiunile descrise mai sus, înlocuind, desigur, numărul 2015 cu numărul 2016. Dar fișierul Class1.cs din noul proiect îl ștergem.

Facem un proiect de plugin cu compilare pentru diferite versiuni Revit/AutoCAD

Fișierul cu codul necesar – Class1.cs – îl avem deja și trebuie doar să inserăm o legătură către acesta în noul proiect. Există două căi de inserare a legăturilor:

  1. Îndelungată – facem clic dreapta pe proiect, alegem opțiunea „Adăugați” -> „Element existent”, în fereastra care se deschide căutăm fișierul dorit și în loc de opțiunea „Adăugați” alegem opțiunea „Adăugați ca legătură»

Facem un proiect de plugin cu compilare pentru diferite versiuni Revit/AutoCAD

  1. Scurt – direct în Explorer-ul soluției alegem fișierul necesar (sau chiar fișiere) și le tragem în noul proiect ținând apăsată tasta Alt. Când trageți, veți observa că la apăsarea tastei Alt, cursorul de la mouse se va schimba dintr-un semn de plus într săgeată.
    UPD: Am adus un pic de confuzie în acest paragraf — pentru a muta mai multe fișiere ar trebui să țineți apăsat pe Shift+Alt!

După efectuarea procedurii, în al doilea proiect va apărea un fișier Class1.cs cu pictograma corespunzătoare (săgeata albastră):

Facem un proiect de plugin cu compilare pentru diferite versiuni Revit/AutoCAD

Când editați codul în fereastra editorului, puteți alege și în contextul cărui proiect să vizualizați codul, ceea ce vă va permite să vedeți și să editați codul cu diferite simboluri de compilare condiționată:

Facem un proiect de plugin cu compilare pentru diferite versiuni Revit/AutoCAD

Conform acestui model, creăm toate celelalte proiecte (2017-2020). O soluție rapidă — dacă trageți fișiere în Explorer-ul soluției nu din proiectul de bază, ci din proiectul în care sunt deja inserate ca legătură, atunci nu trebuie să apăsați tasta Alt!

Varianta descrisă este destul de bună până la adăugarea unei noi versiuni a pluginului sau până la adăugarea de noi fișiere în proiect — toate acestea devin foarte complicate. De curând, am realizat cum să soluționez toate acestea cu un singur proiect și trecem la a doua metodă

Magia configurațiilor

După ce ați citit până aici, s-ar putea să exclamați „De ce ai descris prima metodă, dacă articolul este imediat despre a doua?!”. Am descris totul pentru a fi mai clar de ce avem nevoie de simboluri de compilare condiționată și în ce locuri se diferențiază proiectele noastre. Și acum ne este mai clar ce diferențe specifice trebuie să implementăm, lăsând un singur proiect.

Și pentru a fi totul mai evident, nu vom crea un proiect nou, ci vom face modificări în proiectul nostru actual, creat prin prima metodă.

Așadar, în primul rând, ștergem din soluție toate proiectele, cu excepția celui principal (care conține direct fișierele). Adică proiectele pentru versiunile 2016-2020. Deschidem folderul cu soluția și ștergem acolo folderele acestor proiecte.

Ne-a rămas în soluție un singur proiect — MySuperPluginForRevit_2015.. Deschidem proprietățile acestuia și:

  1. În tab-ul „Aplicație” din numele pachetului eliminăm sufixul _2015 (mai târziu va deveni clar de ce)
  2. În tab-ul „Compilare» eliminăm simbolul compilării condiționate R2015 din câmpul corespunzător

Notă: în ultima versiune Visual Studio există un bug – simbolurile compilării condiționate nu sunt afișate în fereastra proprietăților proiectului, deși ele există. Dacă observați această eroare, va trebui să le eliminați manual din fișierul .csproj. Totuși, trebuie să lucrăm în el, așa că haideți să citim mai departe.

Renunțăm la denumirea proiectului în fereastra Exploatator soluții, eliminând sufixul _2015 și apoi eliminăm proiectul din soluție. Acest lucru este necesar pentru a menține ordinea și pentru a satisface perfecționiștii! Deschidem folderul soluției noastre, redenumim acolo folderul proiectului în același mod și încărcăm proiectul înapoi în soluție.

Deschidem managerul configurațiilor. Configurația noastră Release în principiu nu ne va fi necesară, așa că o eliminăm. Creăm configurații noi cu denumirile deja cunoscute R2015, R2016, …, R2020. Rețineți că nu este nevoie să copiati parametrii din alte configurații și nu este nevoie să creați configurații de proiect:

Facem un proiect de plugin cu compilare pentru diferite versiuni Revit/AutoCAD

Ne îndreptăm spre folderul cu proiectul și deschidem fișierul cu extensia .csproj în editorul dumneavoastră preferat. De altfel, îl puteți deschide și în Visual Studio – trebuie să descărcați proiectul și apoi în meniul contextual va apărea opțiunea dorită:

Facem un proiect de plugin cu compilare pentru diferite versiuni Revit/AutoCAD

Editarea în Visual Studio este chiar preferată, deoarece editorul aliniază și oferă sugestii.

În fișier, vom vedea elementele PropertyGroup – în partea de sus este cel general, iar apoi urmează cele condiționate. Aceste elemente definesc proprietățile proiectului în timpul compilării. Primul element, care nu are condiții, definește proprietățile generale, iar elementele cu condiții, respectiv, modifică unele proprietăți în funcție de configurații.

Trecem la elementul general (primul) PropertyGroup și verificăm proprietatea AssemblyName – acesta este numele asamblării și trebui să fie fără sufix _2015. Dacă există un sufix, îl eliminăm.

Găsim elementul cu condiția

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">

Acesta nu ne trebuie – îl eliminăm.

Elementul cu condiția

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

va fi necesar pentru lucrul în etapa de dezvoltare și depanare a codului. Puteți schimba proprietățile sale conform nevoilor dumneavoastră – stabiliți căi de ieșire diferite, schimbați simbolurile compilării condiționate etc.

Acum creăm noi elemente PropertyGroup pentru configurațiile noastre. În aceste elemente, este suficient să definim patru proprietăți:

  • OutputPath – folderul de ieșire. Eu stabilesc valoarea standard binR20xx
  • DefineConstants – simboluri de compilare condiționată. Trebuie să specificați o valoare TRACE;R20xx
  • TargetFrameworkVersion – versiunea platformei. Pentru diferite versiuni ale Revit API, trebuie să specificați platforme diferite.
  • AssemblyName – numele asamblării (adică numele fișierului). Puteți scrie direct numele dorit al asamblării, dar pentru universalitate, vă sfătuiesc să scrieți valoarea $(AssemblyName)_20xx. Pentru aceasta, anterior am eliminat sufixul din numele asamblării

Cel mai important aspect al acestor elemente este că le puteți pur și simplu copia în alte proiecte fără a le modifica. Ulterior, în articol voi atașa tot conținutul fișierului .csproj.

Bine, am înțeles proprietățile proiectului – nu este complicat. Dar ce facem cu bibliotecile conectate (pachetele NuGet). Dacă ne uităm mai departe, vom vedea că bibliotecile conectate sunt specificate în elementele ItemGroup. Dar, din păcate, acest element procesează greșit condițiile, la fel ca elementul PropertyGroup. Poate că este chiar un bug în Visual Studio, dar dacă specificați mai multe elemente ItemGroup cu condițiile configurațiilor, iar în interior introduceți diferite linkuri către pachetele NuGet, atunci la schimbarea configurației, toate pachetele specificate se vor conecta la proiect.

Elementul Choose, care funcționează după logica familiară nouă if-then-else.

Folosind elementul Choose, specificăm diferite pachete NuGet pentru diferite configurații:

Tot conținutul 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
          runtime

Vă rog să rețineți că într-una dintre condiții am specificat două configurații prin SAU (Or). Astfel, va fi conectat pachetul necesar la configurare Debug.

Și iată că aproape totul este perfect. Încărcăm din nou proiectul, activăm configurația dorită, alegem din meniul contextual al soluției (nu al proiectului) opțiunea „Restaurare toate pachetele NuGet” și vedem cum ne schimbă pachetele.

Facem un proiect de plugin cu compilare pentru diferite versiuni Revit/AutoCAD

Și acum, în acest stadiu, am ajuns într-un impas – pentru a compila toate configurațiile deodată am putea folosi construcția în lot (meniul „Compilare” -> „Construcție în lot”), dar la schimbarea configurațiilor nu se restabilesc automat pachetele. Nici în timpul compilării proiectului nu se întâmplă, deși, în teorie, ar trebui să se întâmple. Nu am găsit nicio soluție standard pentru această problemă și cel mai probabil este un bug în Visual Studio.

Așadar, pentru construcția în lot s-a decis să folosim un sistem de construcție automatizată Nuke. De fapt, nu am dorit acest lucru, deoarece consider că este excesiv în cadrul dezvoltării pluginurilor, dar momentan nu văd altă soluție. Iar la întrebarea „De ce Nuke?” răspunsul este simplu – folosim la muncă.

Așadar, să trecem la folderul soluției noastre (nu al proiectului), apăsăm tasta Shift și faceți clic dreapta pe un spațiu gol din folder – în meniul contextual selectăm opțiunea „Deschis fereastra PowerShell aici».

Facem un proiect de plugin cu compilare pentru diferite versiuni Revit/AutoCAD

Dacă nu aveți instalat nuke, atunci mai întâi scrieți comanda

dotnet tool install Nuke.GlobalTool –global

Acum scrieți comanda nuke și vi se va propune să configurați nuke pentru proiectul curent. Nu știu cum să scriu acest lucru corect în română – în engleză va fi scris Could not find .nuke file. Do you want to setup a build? [y/n]

Apăsăm tasta Y și apoi vor urma pașii de configurare. Avem nevoie de cea mai simplă variantă cu utilizarea MSBuild, așa că răspundem ca în captura de ecran:

Facem un proiect de plugin cu compilare pentru diferite versiuni Revit/AutoCAD

Să trecem la Visual Studio, care ne va propune să reîncărcăm soluția, deoarece a fost adăugat un nou proiect. Reîncărcăm soluția și vedem că a apărut un proiect build în care ne interesează doar un singur fișier – Build.cs

Facem un proiect de plugin cu compilare pentru diferite versiuni Revit/AutoCAD

Deschidem acest fișier și scriem un script pentru construirea proiectului în toate configurațiile. Sau putem folosi scriptul meu, pe care îl puteți edita după cum doriți:

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;

    // Dacă numele soluției și numele proiectului (pluginului) sunt diferite, atunci indicați aici numele proiectului (pluginului)
    string PluginName => Solution.Name;

    Target Compile => _ => _
        .Executes(() =>
        {
            var project = Solution.GetProject(PluginName);
            if (project == null)
                throw new FileNotFoundException("Nu a fost găsit!");

            var build = new List();
            foreach (var (_, c) in project.Configurations)
            {
                var configuration = c.Split("|")[0];

                if (configuration == "Debug" || build.Contains(configuration))
                    continue;

                Logger.Normal($"Configurație: {configuration}");

                build.Add(configuration);

                MSBuild(_ => _
                    .SetProjectFile(project.Path)
                    .SetConfiguration(configuration)
                    .SetTargets("Restaurare"));
                MSBuild(_ => _
                    .SetProjectFile(project.Path)
                    .SetConfiguration(configuration)
                    .SetTargets("Recompilare"));
            }
        });
}

Ne întoarcem la fereastra PowerShell și scriem din nou comanda nuke (putem scrie comanda nuke specificând necesarul Target. Dar avem unul Target, care se rulează implicit). După ce apăsăm tasta Enter, ne vom simți ca adevărați hackeri, deoarece, ca într-un film, va începe compilarea automată a proiectului nostru pentru diferite configurații.

Apropo, putem folosi PowerShell direct din Visual Studio (meniul „Vizualizare” -> „Alte feronțuri” -> „Console manager de pachete„), dar acolo totul va fi alb-negru, ceea ce nu este foarte confortabil.

Aceasta este sfârșitul articolului meu. Sunt sigur că veți putea înțelege varianta pentru AutoCAD singuri. Sper că materialul prezentat aici va găsi „clienții” săi.

Vă mulțumesc pentru atenție!

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster