Kami membuat satu proyek plugin dengan kompilasi untuk berbagai versi Revit / AutoCAD



Ketika mengembangkan plugin untuk aplikasi CAD ( dalam kasus saya, AutoCAD, Revit, dan Renga), satu masalah muncul seiring waktu - versi program baru keluar, perubahan API dan versi plugin baru perlu dibuat.


Ketika Anda hanya memiliki satu plugin atau Anda masih pemula secara otodidak dalam hal ini, Anda dapat membuat salinan proyek, mengubah tempat-tempat yang diperlukan di dalamnya dan membangun versi baru plugin. Oleh karena itu, perubahan kode selanjutnya akan memerlukan peningkatan beberapa kali dalam biaya tenaga kerja.


Ketika Anda memperoleh pengalaman dan pengetahuan, Anda akan menemukan beberapa cara untuk mengotomatisasi proses ini. Saya pergi dengan cara ini dan saya ingin memberi tahu Anda apa yang saya datangi pada akhirnya dan betapa nyamannya itu.


Untuk memulai, pertimbangkan metode yang sudah jelas dan sudah lama saya gunakan


Tautan ke file proyek


Dan untuk membuat semuanya sederhana, jelas dan mudah dimengerti, saya akan menjelaskan semuanya menggunakan contoh abstrak dari pengembangan plug-in.


Buka Visual Studio (saya memiliki versi Komunitas 2019. Dan ya - dalam bahasa Rusia) dan buat solusi baru. Sebut saja MySuperPluginForRevit


image1

Kami akan membuat plugin untuk Revit untuk versi 2015-2020. Oleh karena itu, kami akan membuat proyek baru dalam solusi (Net Framework Class Library) dan menyebutnya MySuperPluginForRevit_2015


image2

Kami perlu menambahkan tautan ke Revit API. Tentu saja, kami dapat menambahkan tautan ke file lokal (Anda perlu menginstal semua SDK yang diperlukan atau semua versi Revit), tetapi kami akan langsung ke jalur yang benar dan menghubungkan paket NuGet. Anda tidak dapat menemukan sejumlah kecil paket, tetapi saya akan menggunakan paket saya sendiri.


Setelah menghubungkan paket, klik kanan pada item " Tautan " dan pilih " Transfer paket.konfig ke PackageReference ... " di menu


image3

Jika Anda tiba-tiba panik pada saat ini, karena jendela properti paket tidak memiliki item penting " Salin secara lokal ", yang pasti perlu Anda setel ke false , maka jangan panik - buka folder proyek dan buka file dengan ekstensi. csproj di editor yang nyaman (saya menggunakan Notepad ++) dan kami menemukan ada catatan tentang paket kami. Ini terlihat seperti ini sekarang:


<PackageReference Include="ModPlus.Revit.API.2015"> <Version>1.0.0</Version> </PackageReference> 

Tambahkan properti <ExcludeAssets> runtime </ExcludeAssets> padanya . Ternyata seperti ini:


 <PackageReference Include="ModPlus.Revit.API.2015"> <Version>1.0.0</Version> <ExcludeAssets>runtime</ExcludeAssets> </PackageReference> 

Sekarang, ketika membangun proyek, file dari paket tidak akan disalin ke folder output.
Kami melangkah lebih jauh - langsung membayangkan bahwa plugin kami akan menggunakan sesuatu dari API Revit, yang telah berubah dengan rilis versi baru. Baik, atau adil, kita perlu mengubah sesuatu dalam kode, tergantung pada versi Revit yang kita buat plugin. Untuk mengatasi perbedaan kode tersebut, kami akan menggunakan simbol kompilasi bersyarat. Buka properti proyek, buka tab " Majelis " dan di bidang " Simbol Kompilasi Bersyarat " tulis R2015 .


image4

Perhatikan bahwa simbol harus ditambahkan untuk konfigurasi Debug dan konfigurasi Release.


Nah, ketika kita berada di jendela properti, kita segera pergi ke tab " Aplikasi " dan di bidang " Default namespace " hapus akhiran _2015 sehingga namespace kita bersifat universal dan independen dari nama assembly:


image5

Dalam kasus saya, dalam produk akhir, plugin semua versi dimasukkan ke dalam satu folder, jadi nama assembly saya tetap dengan akhiran form _20xx . Tetapi Anda juga dapat menghapus akhiran dari nama rakitan jika lokasi file dalam folder yang berbeda diasumsikan.


Kami meneruskan ke kode file Class1.cs dan mensimulasikan beberapa kode di sana, dengan mempertimbangkan berbagai versi 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", "Hello Revit 2015"); #elif R2016 TaskDialog.Show("ModPlus", "Hello Revit 2016"); #elif R2017 TaskDialog.Show("ModPlus", "Hello Revit 2017"); #elif R2018 TaskDialog.Show("ModPlus", "Hello Revit 2018"); #elif R2019 TaskDialog.Show("ModPlus", "Hello Revit 2019"); #elif R2020 TaskDialog.Show("ModPlus", "Hello Revit 2020"); #endif return Result.Succeeded; } } } 

Saya segera memperhitungkan semua versi Revit di atas versi 2015 (yang pada saat penulisan) dan segera memperhitungkan kehadiran simbol kompilasi bersyarat, yang saya buat menggunakan templat yang sama.


Kami lolos ke sorotan utama. Kami membuat proyek baru dalam solusi kami, hanya untuk versi plug-in untuk Revit 2016. Kami mengulangi semua langkah yang dijelaskan di atas, masing-masing, mengganti angka 2015 dengan angka 2016. Tetapi kami menghapus file Class1.cs dari proyek baru.


image6

File dengan kode yang diinginkan - Class1.cs - sudah kita miliki dan kita hanya perlu menyisipkan tautan ke sana dalam proyek baru. Ada dua cara untuk menyisipkan tautan:


  1. Klik lama pada proyek dengan tombol kanan mouse, pilih item " Tambah " -> " Elemen yang ada ", di jendela yang terbuka, cari file yang diinginkan dan alih-alih opsi " Tambah " pilih opsi " Tambah sebagai tautan "

image7

  1. Pendek - kanan di explorer solusi, pilih file yang diinginkan (atau bahkan file. Atau bahkan seluruh folder) dan seret dan jatuhkan ke proyek baru dengan tombol Alt ditekan. Saat menarik, Anda akan melihat bahwa ketika Anda menekan tombol Alt, kursor pada mouse akan berubah dari tanda tambah menjadi panah.
    UPD: Saya membuat sedikit kebingungan di bagian ini - untuk mentransfer banyak file, tahan Shift + Alt !

Setelah prosedur, kita akan melihat dalam proyek kedua file Class1.cs dengan ikon yang sesuai (panah biru):


image8

Saat mengedit kode di jendela editor, Anda juga dapat memilih dalam konteks proyek mana yang akan menampilkan kode, yang memungkinkan Anda melihat kode yang sedang diedit dengan berbagai simbol kompilasi bersyarat:


image9

Di bawah skema ini, kami membuat semua proyek lain (2017-2020). Peretasan kehidupan - jika Anda menyeret dan meletakkan file dalam solusi explorer bukan dari proyek dasar, tetapi dari proyek di mana mereka sudah dimasukkan sebagai tautan, maka Anda tidak dapat menahan tombol Alt!


Opsi yang dijelaskan cukup baik sampai saat versi baru plugin ditambahkan atau sampai file baru ditambahkan ke proyek - semua ini menjadi sangat suram. Dan baru-baru ini, tiba-tiba saya tiba-tiba menyadari bagaimana menyelesaikan semua ini dengan satu proyek dan kami beralih ke metode kedua


Sihir konfigurasi


Setelah membaca di sini, Anda dapat berseru, "Apa yang Anda jelaskan tentang metode pertama, jika artikelnya adalah tentang yang kedua?!" Dan saya menggambarkan semuanya untuk memperjelas mengapa kita membutuhkan simbol kompilasi bersyarat dan di tempat mana proyek kami berbeda. Dan sekarang menjadi lebih jelas bagi kami perbedaan proyek spesifik apa yang perlu kami terapkan, hanya menyisakan satu proyek.


Dan agar semuanya lebih jelas, kami tidak akan membuat proyek baru, tetapi akan membuat perubahan pada proyek kami saat ini, dibuat dengan cara pertama.


Jadi, pertama-tama, kami menghapus semua proyek dari solusi, kecuali yang utama (berisi file secara langsung). Yaitu proyek untuk versi 2016-2020. Buka folder solusi dan hapus folder proyek-proyek ini di sana.


Kami memiliki satu solusi tersisa dalam solusi - MySuperPluginForRevit_2015 . Kami membuka propertinya dan:


  1. Pada tab Aplikasi , hapus sufiks _2015 dari nama rakitan (itu akan menjadi jelas mengapa)
  2. Pada tab " Majelis ", hapus simbol kompilasi bersyarat R2015 dari bidang yang sesuai

Catatan: di Visual Studio versi terbaru terdapat kesalahan - simbol kompilasi bersyarat tidak ditampilkan di jendela properti proyek, meskipun ada. Jika Anda memiliki kesalahan ini, maka Anda harus menghapusnya secara manual dari file .csproj. Namun, kami masih bekerja di dalamnya, jadi baca terus.

Ganti nama proyek di jendela solusi explorer dengan menghapus akhiran _2015 dan kemudian menghapus proyek dari solusi. Ini diperlukan untuk menjaga ketertiban dan perasaan perfeksionis! Kami membuka folder solusi kami, mengganti nama folder proyek di sana dengan cara yang sama dan memuat proyek kembali ke solusi.


Buka manajer konfigurasi. Kami, pada prinsipnya, tidak akan memerlukan konfigurasi Rilis , jadi kami menghapusnya. Kami membuat konfigurasi baru dengan nama R2015 , R2016 , ..., R2020 yang sudah tidak asing lagi bagi kami. Harap dicatat bahwa Anda tidak perlu menyalin parameter dari konfigurasi lain dan Anda tidak perlu membuat konfigurasi proyek:


image10

Kami pergi ke folder proyek dan membuka file dengan ekstensi .csproj di editor yang nyaman. Omong-omong, itu juga dapat dibuka di Visual Studio - Anda perlu membongkar proyek dan kemudian item yang tepat akan berada di menu konteks:


image11

Mengedit dalam Visual Studio bahkan lebih disukai, karena editor menyelaraskan dan meminta.


Dalam file kita akan melihat elemen PropertyGroup - di bagian paling atas adalah yang umum, dan kemudian dengan kondisi. Elemen-elemen ini mengatur sifat-sifat proyek selama perakitannya. Elemen pertama, yang tanpa kondisi, masing-masing menetapkan properti umum, dan elemen dengan kondisi, mengubah beberapa properti tergantung pada konfigurasi.


Kita pergi ke elemen umum (pertama) dari PropertyGroup dan melihat properti AssemblyName - ini adalah nama majelis dan kita harus memilikinya tanpa akhiran _2015 . Jika ada sufiks, maka hapus saja.


Kami menemukan elemen dengan kondisi tersebut


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

Kami tidak membutuhkannya - kami menghapusnya.


Ketentuan klausa


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

Ini akan diperlukan untuk bekerja pada tahap pengembangan dan debugging kode. Anda dapat mengubah propertinya sesuai kebutuhan Anda - mengatur jalur output yang berbeda, mengubah simbol kompilasi bersyarat, dll.


Sekarang buat elemen PropertyGroup baru untuk konfigurasi kami. Dalam elemen-elemen ini, kita hanya perlu menetapkan empat properti:


  • OutputPath adalah folder keluaran. Saya mengatur nilai bin standar \ R20xx
  • DefineConstants - simbol kompilasi bersyarat. Setel TRACE; R20xx
  • TargetFrameworkVersion - versi platform. Untuk versi Revit API yang berbeda, platform yang berbeda harus ditentukan.
  • AssemblyName - nama majelis (mis. Nama file). Anda dapat menulis secara langsung nama majelis yang diinginkan, tetapi untuk universalitas, saya sarankan menulis nilai $ (Nama Majelis) _20xx . Untuk melakukan ini, kami sebelumnya menghapus suffix dari nama assembly

Fitur paling penting dari semua elemen ini adalah bahwa mereka dapat disalin secara sepele ke proyek lain tanpa mengubahnya sama sekali. Nanti di artikel saya akan melampirkan seluruh isi file .csproj.


Kami sudah menemukan properti proyek - tidak sulit. Tetapi apa yang harus dilakukan dengan pustaka plug-in (paket NuGet). Jika kita melihat lebih jauh, kita akan melihat bahwa pustaka plug-in didefinisikan oleh elemen ItemGroup . Tapi ini nasib buruk - elemen ini salah memproses kondisi, seperti elemen PropertyGroup . Mungkin ini bahkan kesalahan Visual Studio, tetapi jika Anda mengatur beberapa elemen ItemGroup dengan kondisi konfigurasi, dan menyisipkan tautan berbeda ke paket NuGet di dalamnya, maka ketika Anda mengubah konfigurasi, semua paket yang ditentukan terhubung ke proyek.


Elemen Choose datang ke bantuan kami, yang bekerja sesuai dengan logika if-then-else yang biasa .


Menggunakan elemen Choose , kami mendefinisikan paket NuGet yang berbeda untuk konfigurasi yang berbeda:


Semua isi csproj
 <?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <ProjectGuid>{5AD738D6-4122-4E76-B865-BE7CE0F6B3EB}</ProjectGuid> <OutputType>Library</OutputType> <AppDesignerFolder>Properties</AppDesignerFolder> <RootNamespace>MySuperPluginForRevit</RootNamespace> <AssemblyName>MySuperPluginForRevit</AssemblyName> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> <Deterministic>true</Deterministic> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> <DebugType>full</DebugType> <Optimize>false</Optimize> <OutputPath>bin\Debug\</OutputPath> <DefineConstants>DEBUG;R2015</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'R2015|AnyCPU' "> <OutputPath>bin\R2015\</OutputPath> <DefineConstants>TRACE;R2015</DefineConstants> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <AssemblyName>$(AssemblyName)_2015</AssemblyName> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'R2016|AnyCPU' "> <OutputPath>bin\R2016\</OutputPath> <DefineConstants>TRACE;R2016</DefineConstants> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <AssemblyName>$(AssemblyName)_2016</AssemblyName> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'R2017|AnyCPU' "> <OutputPath>bin\R2017\</OutputPath> <DefineConstants>TRACE;R2017</DefineConstants> <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> <AssemblyName>$(AssemblyName)_2017</AssemblyName> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'R2018|AnyCPU' "> <OutputPath>bin\R2018\</OutputPath> <DefineConstants>TRACE;R2018</DefineConstants> <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> <AssemblyName>$(AssemblyName)_2018</AssemblyName> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'R2019|AnyCPU' "> <OutputPath>bin\R2019\</OutputPath> <DefineConstants>TRACE;R2019</DefineConstants> <TargetFrameworkVersion>v4.7</TargetFrameworkVersion> <AssemblyName>$(AssemblyName)_2019</AssemblyName> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'R2020|AnyCPU' "> <OutputPath>bin\R2020\</OutputPath> <DefineConstants>TRACE;R2020</DefineConstants> <TargetFrameworkVersion>v4.7</TargetFrameworkVersion> <AssemblyName>$(AssemblyName)_2020</AssemblyName> </PropertyGroup> <ItemGroup> <Reference Include="System" /> <Reference Include="System.Core" /> <Reference Include="System.Xml.Linq" /> <Reference Include="System.Data.DataSetExtensions" /> <Reference Include="Microsoft.CSharp" /> <Reference Include="System.Data" /> <Reference Include="System.Net.Http" /> <Reference Include="System.Xml" /> </ItemGroup> <ItemGroup> <Compile Include="Class1.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> </ItemGroup> <Choose> <When Condition=" '$(Configuration)'=='R2015' "> <ItemGroup> <PackageReference Include="ModPlus.Revit.API.2015"> <Version>1.0.0</Version> <ExcludeAssets>runtime</ExcludeAssets> </PackageReference> </ItemGroup> </When> <When Condition=" '$(Configuration)'=='R2016' "> <ItemGroup> <PackageReference Include="ModPlus.Revit.API.2016"> <Version>1.0.0</Version> <ExcludeAssets>runtime</ExcludeAssets> </PackageReference> </ItemGroup> </When> <When Condition=" '$(Configuration)'=='R2017' "> <ItemGroup> <PackageReference Include="ModPlus.Revit.API.2017"> <Version>1.0.0</Version> <ExcludeAssets>runtime</ExcludeAssets> </PackageReference> </ItemGroup> </When> <When Condition=" '$(Configuration)'=='R2018' "> <ItemGroup> <PackageReference Include="ModPlus.Revit.API.2018"> <Version>1.0.0</Version> <ExcludeAssets>runtime</ExcludeAssets> </PackageReference> </ItemGroup> </When> <When Condition=" '$(Configuration)'=='R2019' "> <ItemGroup> <PackageReference Include="ModPlus.Revit.API.2019"> <Version>1.0.0</Version> <ExcludeAssets>runtime</ExcludeAssets> </PackageReference> </ItemGroup> </When> <When Condition=" '$(Configuration)'=='R2020' or '$(Configuration)'=='Debug'"> <ItemGroup> <PackageReference Include="ModPlus.Revit.API.2020"> <Version>1.0.0</Version> <ExcludeAssets>runtime</ExcludeAssets> </PackageReference> </ItemGroup> </When> </Choose> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> </Project> 

Harap dicatat bahwa dalam salah satu kondisi saya tentukan dua konfigurasi melalui OR (Atau) . Dengan demikian, paket yang diperlukan akan terhubung selama konfigurasi Debug .


Dan di sini kita memiliki hampir semuanya sempurna. Kami memuat proyek kembali, mengaktifkan konfigurasi yang kami butuhkan, memanggil item " Kembalikan semua paket NuGet " di menu konteks solusi (bukan proyek) dan melihat bagaimana paket berubah.


image12

Dan pada tahap ini saya terhenti - untuk mengumpulkan semua konfigurasi sekaligus, kita bisa menggunakan batch assembly (menu " Assembly " -> " Batch assembly "), tetapi ketika beralih konfigurasi, paket tidak secara otomatis dikembalikan. Dan ketika merakit proyek, itu juga tidak terjadi, meskipun, secara teori, itu harus terjadi. Saya tidak menemukan solusi untuk masalah ini dengan cara standar. Dan kemungkinan besar ini juga bug dari Visual Studio.


Oleh karena itu, untuk perakitan batch, diputuskan untuk menggunakan sistem perakitan otomatis Nuke khusus. Sebenarnya, saya tidak menginginkan ini, karena saya menganggapnya tidak perlu dalam kerangka pengembangan plug-in, tetapi saat ini saya tidak melihat solusi lain. Dan pertanyaan "Mengapa tepatnya Nuke?" Jawabannya sederhana - kami menggunakannya di tempat kerja.


Jadi, buka folder solusi kami (bukan proyek), tahan tombol Shift dan klik kanan pada tempat kosong di folder - di menu konteks, pilih item " Buka jendela PowerShell di sini ".


image13

Jika Anda belum menginstal nuke , tulis terlebih dahulu perintahnya


 dotnet tool install Nuke.GlobalTool –global 

Sekarang tulis perintah nuklir dan Anda akan diminta untuk mengkonfigurasi nuklir untuk proyek saat ini. Saya tidak tahu cara menulisnya dengan lebih baik dalam bahasa Rusia - File .nuke tidak dapat ditulis dalam bahasa Inggris. Apakah Anda ingin menyiapkan bangunan? [y / n]


Tekan tombol Y dan kemudian akan ada pengaturan langsung. Kami membutuhkan opsi paling sederhana menggunakan MSBuild , jadi kami menjawab seperti pada tangkapan layar:


image14

Mari kita beralih ke Visual Studio, yang akan menawarkan kita untuk memuat ulang solusi, karena proyek baru telah ditambahkan ke dalamnya. Kami me-reboot solusinya dan melihat bahwa kami memiliki proyek pembangunan di mana kami hanya tertarik pada satu file - Build.cs


image15

Buka file ini dan tulis skrip untuk membangun proyek untuk semua konfigurasi. Baik, atau gunakan skrip saya, yang dapat Anda edit sendiri:


 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<Build>(x => x.Compile); [Solution] readonly Solution Solution; // If the solution name and the project (plugin) name are different, then indicate the project (plugin) name here string PluginName => Solution.Name; Target Compile => _ => _ .Executes(() => { var project = Solution.GetProject(PluginName); if (project == null) throw new FileNotFoundException("Not found!"); var build = new List<string>(); foreach (var (_, c) in project.Configurations) { var configuration = c.Split("|")[0]; if (configuration == "Debug" || build.Contains(configuration)) continue; Logger.Normal($"Configuration: {configuration}"); build.Add(configuration); MSBuild(_ => _ .SetProjectFile(project.Path) .SetConfiguration(configuration) .SetTargets("Restore")); MSBuild(_ => _ .SetProjectFile(project.Path) .SetConfiguration(configuration) .SetTargets("Rebuild")); } }); } 

Kami kembali ke jendela PowerShell dan menulis perintah nuklir lagi (Anda dapat menulis perintah nuklir dengan Target yang diinginkan. Tapi kami memiliki satu Target , yang dimulai secara default). Setelah menekan tombol Enter, kita akan merasa seperti peretas sejati, karena seperti dalam sebuah film, proyek kita akan secara otomatis dirakit untuk konfigurasi yang berbeda.


Omong-omong, Anda dapat menggunakan PowerShell langsung dari Visual Studio (menu " Lihat " -> " Jendela lain " -> " Package Manager Console "), tetapi semuanya akan menjadi hitam dan putih, yang sangat tidak nyaman.


Tentang ini, artikel saya sudah selesai. Saya yakin Anda dapat mengetahui sendiri opsi untuk AutoCAD. Saya berharap bahwa materi yang disajikan di sini akan menemukan "pelanggan" -nya.


Terima kasih atas perhatian anda!

Source: https://habr.com/ru/post/id482308/


All Articles