Tworzymy jeden projekt wtyczki z kompilacją pod różne wersje Revit/AutoCAD

Tworzymy jeden projekt wtyczki z kompilacją pod różne wersje Revit/AutoCAD

Podczas tworzenia wtyczek do aplikacji CAD (w moim przypadku to AutoCAD, Revit i Renga) z czasem pojawia się jeden problem – wydawane są nowe wersje programów, zmienia się ich API i należy zaktualizować wtyczki.

Kiedy masz tylko jedną wtyczkę lub dopiero zaczynasz w tej dziedzinie, wystarczy zrobić kopię projektu, wprowadzić zmiany w odpowiednich miejscach i zbudować nową wersję wtyczki. W związku z tym późniejsze modyfikacje kodu będą wiązały się z wielokrotnym zwiększeniem nakładów pracy.

W miarę zdobywania doświadczenia i wiedzy znajdziesz kilka sposobów na zautomatyzowanie tego procesu. Przeszedłem tę drogę i chciałbym opowiedzieć, do czego doszedłem ostatecznie i jak wygodne to stało się.

Na początek przyjrzymy się sposobowi, który jest oczywisty i z którego długo korzystałem

Linki do plików projektu

Aby wszystko było proste, przejrzyste i zrozumiałe, będę wszystko opisywał na abstrakcyjnym przykładzie tworzenia wtyczki.

Otwieramy Visual Studio (moje to wersja Community 2019. I tak – po rosyjsku) i tworzymy nowe rozwiązanie. Nazwijmy je MySuperPluginForRevit

Tworzymy jeden projekt wtyczki z kompilacją pod różne wersje Revit/AutoCAD

Będziemy tworzyć wtyczkę do Revit dla wersji 2015-2020. Dlatego w rozwiązaniu utworzę nowy projekt (Biblioteka klas Net Framework) i nazwiemy go MySuperPluginForRevit_2015

Tworzymy jeden projekt wtyczki z kompilacją pod różne wersje Revit/AutoCAD

Musimy dodać odwołania do API Revit. Oczywiście możemy dodać odwołania do lokalnych plików (będzie trzeba zainstalować wszystkie potrzebne SDK lub wszystkie wersje Revit), ale pójdziemy od razu właściwą drogą i podłączymy pakiet NuGet. Możesz znaleźć sporo pakietów, ale ja użyję swoich własnych.

Po podłączeniu pakietu klikamy prawym przyciskiem myszy na elemencie „Linki” i wybieramy z menu opcję „Przenieś packages.config do PackageReference…»

Tworzymy jeden projekt wtyczki z kompilacją pod różne wersje Revit/AutoCAD

Jeśli przypadkiem w tym momencie poczujesz panikę, ponieważ w oknie właściwości pakietu nie będzie ważnego elementu „Kopiuj lokalnie”, który koniecznie musi być ustawiony na wartość false, to nie ma powodu do paniki – idziemy do folderu z projektem, otwieramy plik z rozszerzeniem .csproj w dogodnym dla siebie edytorze (ja używam Notepad++) i znajdujemy tam zapis o naszym pakiecie. Wygląda on obecnie tak:

1.0.0

Dodajemy mu właściwość runtime. Będzie to wyglądać tak:

1.0.0
  runtime

Teraz przy budowaniu projektu pliki z pakietu nie będą kopiowane do folderu wyjściowego.
Idźmy dalej – od razu wyobraźmy sobie, że nasza wtyczka będzie używać czegoś z API Revit, które zmieniało się z czasem wydania nowych wersji. Albo po prostu musimy zmienić coś w kodzie w zależności od wersji Revit, dla której tworzymy wtyczkę. Aby rozwiązać te różnice w kodzie, użyjemy symboli warunkowej kompilacji. Otworzymy właściwości projektu, przejdziemy na zakładkę „Kompilacja„ i w polu „Oznaczenia warunkowej kompilacji„ wpiszemy R2015.

Tworzymy jeden projekt wtyczki z kompilacją pod różne wersje Revit/AutoCAD

Zauważ, że symbol należy dodać zarówno do konfiguracji Debug, jak i do konfiguracji Release.

A póki jesteśmy w oknie właściwości, to od razu przechodzimy na zakładkę „Aplikacja„ i w polu „Domyślna przestrzeń nazw„ usuwamy sufiks _2015, aby nasza przestrzeń nazw była uniwersalna i niezależna od nazwy zestawu:

Tworzymy jeden projekt wtyczki z kompilacją pod różne wersje Revit/AutoCAD

W moim przypadku w finalnym produkcie wtyczki wszystkich wersji są składane w jednym folderze, więc moje nazwy zestawów pozostają z sufiksem w postaci _20xx. Ale możesz również usunąć sufiks z nazwy zestawu, jeśli planowane jest umieszczenie plików w różnych folderach.

Przechodzimy do kodu pliku Class1.cs i imitujemy pewien kod z uwzględnieniem różnych wersji 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", "Witaj Revit 2015");
#elif R2016
            TaskDialog.Show("ModPlus", "Witaj Revit 2016");
#elif R2017
            TaskDialog.Show("ModPlus", "Witaj Revit 2017");
#elif R2018
            TaskDialog.Show("ModPlus", "Witaj Revit 2018");
#elif R2019
            TaskDialog.Show("ModPlus", "Witaj Revit 2019");
#elif R2020
            TaskDialog.Show("ModPlus", "Witaj Revit 2020");
#endif
            return Result.Succeeded;
        }
    }
}

Od razu uwzględniłem wszystkie wersje Revit powyżej wersji 2015 (które były w momencie pisania artykułu) i od razu uwzględniłem obecność symboli warunkowej kompilacji, które są tworzone według tego samego szablonu.

Przechodzimy do głównej atrakcji. Tworzymy nowy projekt w naszym rozwiązaniu, ale już dla wersji wtyczki pod Revit 2016. Powtarzamy wszystkie opisane powyżej czynności, odpowiednio zamieniając liczbę 2015 na 2016. Ale plik Class1.cs z nowego projektu usuwamy.

Tworzymy jeden projekt wtyczki z kompilacją pod różne wersje Revit/AutoCAD

Plik z potrzebnym kodem – Class1.cs – już mamy i musimy po prostu wstawić na niego link w nowym projekcie. Są dwa sposoby wstawiania linków:

  1. Długi – klikamy prawym przyciskiem myszy na projekcie, wybieramy opcję «Dodaj» -> «Istniejący element», w otwartym oknie znajdujemy odpowiedni plik i zamiast opcji «Dodaj» wybieramy opcję «Dodaj jako powiązanie»

Tworzymy jeden projekt wtyczki z kompilacją pod różne wersje Revit/AutoCAD

  1. Krótki – bezpośrednio w eksploratorze rozwiązań wybieramy odpowiedni plik (lub nawet pliki. Można też przeciągać całe foldery) i przeciągamy je do nowego projektu, trzymając przycisk Alt. Podczas przeciągania zauważysz, że przy naciśniętym klawiszu Alt kursor myszy zmienia się z plusika w strzałkę.
    UPD: Trochę wprowadziłem zamieszania w tym akapicie – aby przenieść kilka plików, należy przytrzymać Shift+Alt!

Po przeprowadzeniu tej procedury w naszym drugim projekcie pojawi się plik Class1.cs z odpowiednią ikoną (niebieska strzałka):

Tworzymy jeden projekt wtyczki z kompilacją pod różne wersje Revit/AutoCAD

Podczas edytowania kodu w oknie edytora możesz również wybierać, w kontekście którego projektu wyświetlać kod, co pozwoli ci na oglądanie i edytowanie kodu przy różnych symbolach warunkowej kompilacji:

Tworzymy jeden projekt wtyczki z kompilacją pod różne wersje Revit/AutoCAD

Na tej podstawie tworzymy wszystkie pozostałe projekty (2017-2020). Lifehack – jeśli przeciągasz pliki w eksploratorze rozwiązań nie z projektu podstawowego, a z projektu, gdzie zostały już dodane jako powiązanie, nie musisz trzymać klawisza Alt!

Opisana opcja jest jak najbardziej dobra do momentu dodania nowej wersji wtyczki lub nowych plików do projektu – wszystko to staje się bardzo uciążliwe. A niedawno nagle zdałem sobie sprawę, jak to wszystko rozwiązać jednym projektem i przechodzimy do drugiego sposobu.

Magia konfiguracji

Doczytawszy tutaj, możesz wykrzyknąć „A po co opisywałeś pierwszy sposób, skoro artykuł od razu o drugim?!”. Opisałem wszystko, aby było jaśniejsze, po co potrzebujemy symboli warunkowej kompilacji i w jakich miejscach nasze projekty się różnią. I teraz staje się jaśniejsze, jakie dokładnie różnice projektów musimy zrealizować, pozostawiając tylko jeden projekt.

I aby wszystko było bardziej oczywiste, nie stworzymy nowego projektu, lecz wprowadzimy zmiany w naszym bieżącym projekcie, stworzonym pierwszym sposobem.

Zatem w pierwszej kolejności usuwamy z rozwiązania wszystkie projekty, poza głównym (zawierającym właściwe pliki). Tzn. projekty dla wersji 2016-2020. Otwieramy folder z rozwiązaniem i usuwamy tam foldery tych projektów.

W naszym rozwiązaniu został tylko jeden projekt — MySuperPluginForRevit_2015. Otwieramy jego właściwości i:

  1. Na zakładce «Aplikacja» z nazwy kompilacji usuwamy sufiks _2015 (później stanie się jasne po co)
  2. Na zakładce «Kompilacja» usuwamy symbol kompilacji warunkowej R2015 z odpowiedniego pola

Uwaga: w najnowszej wersji Visual Studio występuje błąd – symbole kompilacji warunkowej nie wyświetlają się w oknie właściwości projektu, mimo że są obecne. Jeśli masz ten błąd, musisz usunąć je ręcznie z pliku .csproj. Niemniej jednak, wciąż musimy w nim pracować, więc czytamy dalej.

Zmieniamy nazwę projektu w oknie Eksploratora rozwiązań, usuwając sufiks _2015 a następnie usuwamy projekt z rozwiązania. Jest to potrzebne, aby zachować porządek i zaspokoić potrzeby perfekcjonistów! Otwieramy folder naszego rozwiązania, zmieniamy tam nazwę folderu projektu w ten sam sposób i ponownie ładujemy projekt do rozwiązania.

Otwieramy menedżera konfiguracji. Nasza konfiguracja Release w zasadzie nie będzie potrzebna, więc ją usuwamy. Tworzymy nowe konfiguracje o już znanych nam nazwach R2015, R2016, …, R2020. Zwróć uwagę, że nie trzeba kopiować parametrów z innych konfiguracji i nie należy tworzyć konfiguracji projektu:

Tworzymy jeden projekt wtyczki z kompilacją pod różne wersje Revit/AutoCAD

Idziemy do folderu z projektem i otwieramy plik z rozszerzeniem .csproj w wygodnym edytorze. Tak swoją drogą, można go otworzyć również w Visual Studio – należy odładować projekt, a następnie w menu kontekstowym pojawi się odpowiednia opcja:

Tworzymy jeden projekt wtyczki z kompilacją pod różne wersje Revit/AutoCAD

Edycja w Visual Studio jest nawet bardziej preferowana, ponieważ edytor wyrównuje i podpowiada.

W pliku zobaczymy elementy PropertyGroup – na samej górze znajdują się ogólne, a następnie te z warunkami. Te elementy określają właściwości projektu podczas jego kompilacji. Pierwszy element, który nie ma warunków, określa ogólne właściwości, a elementy z warunkami zmieniają niektóre właściwości w zależności od konfiguracji.

Przechodzimy do ogólnego (pierwszego) elementu PropertyGroup i sprawdzamy właściwość AssemblyName – to nazwa zestawu i powinna być bez sufiksu _2015. Jeśli sufiks jest obecny, usuwamy go.

Znaleźć element z warunkiem

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

Nie jest nam potrzebny – usuwamy go.

Element z warunkiem

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

będzie potrzebny do pracy na etapie rozwoju i debugowania kodu. Możesz zmieniać jego właściwości według własnych potrzeb – ustawiać różne ścieżki wyjściowe, zmieniać symbole kompilacji warunkowej itp.

Teraz tworzymy nowe elementy PropertyGroup dla naszych konfiguracji. W tych elementach wystarczy ustawić cztery właściwości:

  • OutputPath – folder wyjściowy. Ustawiłem standardową wartość binR20xx
  • DefineConstants – symbole kompilacji warunkowej. Należy ustawić wartość TRACE;R20xx
  • TargetFrameworkVersion – wersja platformy. Dla różnych wersji Revit API trzeba ustawić różne platformy.
  • AssemblyName – nazwa zestawu (tj. nazwa pliku). Można wpisać bezpośrednio potrzebną nazwę zestawu, ale dla uniwersalności polecam wpisać wartość $(AssemblyName)_20xx. W tym celu wcześniej usunęliśmy sufiks z nazwy zestawu

Najważniejsza cechą wszystkich tych elementów jest to, że można je po prostu kopiować do innych projektów, w ogóle ich nie zmieniając. W dalszej części artykułu dołączę całą zawartość pliku .csproj.

Dobrze, rozwiązałem właściwości projektu – to nie jest trudne. Ale co zrobić z bibliotekami dołączanymi (pakietami NuGet). Jeśli spojrzymy dalej, zobaczymy, że dołączane biblioteki są definiowane w elementach ItemGroup. Ale jest problem – ten element nieprawidłowo obsługuje warunki, jak element PropertyGroup. Może to nawet być błąd Visual Studio, ale jeśli określić kilka elementów ItemGroup z warunkami konfiguracji, a wewnątrz wstawić różne linki do pakietów NuGet, to przy zmianie konfiguracji do projektu dołączane są wszystkie wskazane pakiety.

Na pomoc przychodzi nam element Choose, który działa według znanej nam logiki if-then-else.

Używając elementu Choose, definiujemy różne pakiety NuGet dla różnych konfiguracji:

Cała zawartość 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

Zauważ, że w jednym z warunków podałem dwie konfiguracje przez LUB (Or). W ten sposób odpowiedni pakiet będzie dołączany przy konfiguracji Debug.

I oto prawie wszystko jest idealne. Ładujemy projekt z powrotem, włączamy potrzebną nam konfigurację, wybieramy w menu kontekstowym rozwiązania (nie projektu) opcję „Przywróć wszystkie pakiety NuGet” i widzimy, jak zmieniają się nasze pakiety.

Tworzymy jeden projekt wtyczki z kompilacją pod różne wersje Revit/AutoCAD

I na tym etapie utknąłem – aby zbudować wszystkie konfiguracje, moglibyśmy skorzystać z budowy pakietów (menu „Kompilacja» -> «Pakietowa budowa”), ale podczas przełączania konfiguracji nie następuje automatyczne przywracanie pakietów. I podczas budowy projektu również się to nie dzieje, chociaż, teoretycznie, powinno. Nie znalazłem rozwiązania tego problemu standardowymi środkami i prawdopodobnie to też jest błąd Visual Studio.

Dlatego postanowiono wykorzystać specjalny system automatyzacji budowy Nuke. Tak naprawdę nie chciałem tego, ponieważ uważam, że to zbędne w kontekście rozwoju wtyczek, ale w tej chwili nie widzę innego rozwiązania. A na pytanie „Dlaczego właśnie Nuke?” odpowiedź jest prosta – używamy w pracy.

Zatem przechodzimy do folderu naszego rozwiązania (nie projektu), przytrzymujemy klawisz Shift i klikamy prawym przyciskiem myszy w pustym miejscu w folderze – w menu kontekstowym wybieramy opcję „Otwórz okno PowerShell tutaj».

Tworzymy jeden projekt wtyczki z kompilacją pod różne wersje Revit/AutoCAD

Jeśli nie masz zainstalowanego nuke, najpierw wpisz polecenie

dotnet tool install Nuke.GlobalTool –global

Teraz wpisz polecenie nuke i zostanie ci zaproponowane skonfigurowanie nuke dla aktualnego projektu. Nie wiem, jak to lepiej napisać po polsku – po angielsku będzie napisane Could not find .nuke file. Do you want to setup a build? [y/n]

Naciskamy klawisz Y, a następnie nastąpią bezpośrednie kroki konfiguracji. Potrzebujemy najprostszej opcji z użyciem MSBuild, więc odpowiadamy zgodnie z zrzutem ekranu:

Tworzymy jeden projekt wtyczki z kompilacją pod różne wersje Revit/AutoCAD

Przechodzimy do Visual Studio, która zaproponuje nam ponowne załadowanie rozwiązania, ponieważ dodano nowy projekt. Załaduj ponownie rozwiązanie i zobacz, że pojawił się projekt build w którym interesuje nas tylko jeden plik – Build.cs

Tworzymy jeden projekt wtyczki z kompilacją pod różne wersje Revit/AutoCAD

Otwieramy ten plik i piszemy skrypt do budowy projektu pod wszystkie konfiguracje. Lub używamy mojego skryptu, który możesz dostosować do siebie:

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;

    // Jeśli nazwa rozwiązania i nazwa projektu (wtyczki) są różne, wskaż tutaj nazwę projektu (wtyczki)
    string PluginName => Solution.Name;

    Target Compile => _ => _
        .Executes(() =>
        {
            var project = Solution.GetProject(PluginName);
            if (project == null)
                throw new FileNotFoundException("Nie znaleziono!");

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

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

                Logger.Normal($"Konfiguracja: {configuration}");

                build.Add(configuration);

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

Wracamy do okna PowerShell i ponownie wpisujemy polecenie nuke (można wpisać polecenie nuke z podaniem potrzebnego Cele. Ale mamy jeden Cele, który uruchamia się domyślnie). Po naciśnięciu klawisza Enter poczujemy się jak prawdziwi hakerzy, ponieważ jak w filmie nastąpi automatyczna budowa naszego projektu dla różnych konfiguracji.

A propos, można używać PowerShell bezpośrednio z Visual Studio (menu „Widok» -> «Inne okna» -> «Konsola Menedżera pakietów”), ale tam wszystko będzie czarno-białe, co nie jest zbyt wygodne.

Na tym kończę mój artykuł. Jestem pewien, że z wersją dla AutoCADa poradzisz sobie sam. Mam nadzieję, że zamieszczony tutaj materiał znajdzie swoich „klientów”.

Dziękuję za uwagę!

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster