
Podczas tworzenia wtyczek do aplikacji CAD ( 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

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

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…»

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.0Dodajemy mu właściwość runtime. Będzie to wyglądać tak:
1.0.0
runtimeTeraz 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.

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:

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.

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:
- 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»

- 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):

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:

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:
- Na zakładce «Aplikacja» z nazwy kompilacji usuwamy sufiks _2015 (później stanie się jasne po co)
- 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:

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:

Edycja w Visual Studio jest nawet bardziej preferowana, ponieważ edytor wyrównuje i podpowiada.
W pliku zobaczymy elementy – 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 . 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 , 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
runtimeZauważ, ż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.

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 . 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».

Jeśli nie masz zainstalowanego nuke, najpierw wpisz polecenie
dotnet tool install Nuke.GlobalTool –globalTeraz 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:

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

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
