Vytváříme jeden projekt pluginu s kompilací pro různé verze Revit / AutoCAD

Vytváříme jeden projekt pluginu s kompilací pro různé verze Revit / AutoCAD

Při vývoji pluginů pro CAD aplikace (v mém případě jedná se o AutoCAD, Revit a Renga) postupem času se objevuje jeden problém - vycházejí nové verze programů, je potřeba udělat změny jejich API a nové verze pluginů.

Pokud máte pouze jeden plugin nebo jste v této věci stále začátečníkem samoukem, můžete si jednoduše vytvořit kopii projektu, změnit v něm potřebná místa a sestavit novou verzi pluginu. V souladu s tím povedou následné změny kodexu k mnohonásobnému zvýšení mzdových nákladů.

Jak získáte zkušenosti a znalosti, najdete několik způsobů, jak tento proces automatizovat. Šel jsem touto cestou a chci vám říct, k čemu jsem skončil a jak je to pohodlné.

Nejprve se podívejme na metodu, která je zřejmá a kterou používám již dlouhou dobu

Odkazy na soubory projektu

A aby vše bylo jednoduché, jasné a srozumitelné, popíšu vše na abstraktním příkladu vývoje pluginu.

Otevřeme Visual Studio (mám verzi Community 2019. A ano – v ruštině) a vytvoříme nové řešení. Nazvěme to MySuperPluginForRevit

Vytváříme jeden projekt pluginu s kompilací pro různé verze Revit / AutoCAD

Uděláme plugin pro Revit pro verze 2015-2020. V řešení proto vytvořím nový projekt (Net Framework Class Library) a zavolám jej MySuperPluginForRevit_2015

Vytváříme jeden projekt pluginu s kompilací pro různé verze Revit / AutoCAD

Potřebujeme přidat odkazy na Revit API. Samozřejmě můžeme přidat odkazy na místní soubory (budete muset nainstalovat všechny potřebné SDK nebo všechny verze Revitu), ale půjdeme rovnou a zahrneme balíček NuGet. Balíčků se dá najít docela dost, ale já použiju svoje.

Po připojení balíčku klikněte pravým tlačítkem myši na položku "reference"a vyberte položku"Přesunout packages.config do PackageReference...»

Vytváříme jeden projekt pluginu s kompilací pro různé verze Revit / AutoCAD

Pokud náhle v tomto okamžiku začnete panikařit, protože v okně vlastností balíčku nebude žádná důležitá položka “zkopírujte lokálně“, což rozhodně musíme nastavit nepravdivý, pak nepropadejte panice - přejděte do složky projektu, otevřete soubor s příponou .csproj v editoru, který vám vyhovuje (používám Notepad ++) a najděte tam záznam o našem balíčku. Teď vypadá takto:

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

Přidání vlastnosti runtime. Dopadne to takto:

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

Nyní při sestavování projektu nebudou soubory z balíčku zkopírovány do výstupní složky.
Pojďme napřed – představte si, že náš plugin bude využívat něco z Revit API, které se postupem času měnilo s vydáváním nových verzí. No, nebo prostě potřebujeme změnit něco vlastního v kódu v závislosti na verzi Revitu, pro kterou plugin vyrábíme. K vyřešení takových rozdílů v kódu použijeme symboly podmíněné kompilace. Otevřete vlastnosti projektu, přejděte na kartu "shromáždění"a v terénu"Zápis podmíněné kompilace" napsat R2015.

Vytváříme jeden projekt pluginu s kompilací pro různé verze Revit / AutoCAD

Všimněte si, že symbol musí být přidán pro konfiguraci ladění i konfiguraci uvolnění.

Když jsme v okně vlastností, okamžitě přejdeme na kartu „Aplikace"a v terénu"Výchozí jmenný prostor»odstranit příponu _2015takže náš jmenný prostor je univerzální a nezávislý na názvu sestavení:

Vytváříme jeden projekt pluginu s kompilací pro různé verze Revit / AutoCAD

V mém případě jsou ve finálním produktu přidány pluginy všech verzí do jedné složky, takže moje názvy sestav zůstávají s příponou formuláře _20хх. Ale můžete také odstranit příponu z názvu sestavení, pokud očekáváte, že soubory budou umístěny v různých složkách.

Pojďme ke kódu souboru Třída1.cs a nasimulovat tam nějaký kód s přihlédnutím k různým verzím Revitu:

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;
        }
    }
}

Okamžitě jsem vzal v úvahu všechny verze Revitu nad verzí 2015 (které byly v době psaní článku) a okamžitě vzal v úvahu přítomnost symbolů podmíněné kompilace, které vytvářím podle stejné šablony.

Pojďme k hlavnímu highlightu. V našem řešení vytvoříme nový projekt, pouze pro verzi plug-inu pod Revit 2016. Opakujeme všechny výše popsané kroky, respektive nahradíme číslo 2015 číslem 2016. Ale soubor Třída1.cs odstraněny z nového projektu.

Vytváříme jeden projekt pluginu s kompilací pro různé verze Revit / AutoCAD

Soubor s požadovaným kódem - Třída1.cs - již máme a stačí na něj vložit odkaz do nového projektu. Odkazy lze vložit dvěma způsoby:

  1. Dlouho - klikněte na projekt pravým tlačítkem myši, vyberte položku "přidat„->“Stávající prvek", v okně, které se otevře, najděte požadovaný soubor a místo možnosti"přidat» vybrat možnost «Přidat jako odkaz»

Vytváříme jeden projekt pluginu s kompilací pro různé verze Revit / AutoCAD

  1. krátký - přímo v průzkumníku řešení vyberte požadovaný soubor (nebo dokonce soubory. Nebo dokonce celé složky) a přetáhněte jej do nového projektu při stisknuté klávese Alt. Při přetahování uvidíte, že po stisknutí klávesy Alt se kurzor na myši změní ze znaménka plus na šipku.
    UPD: V tomto odstavci jsem udělal trochu zmatek - pro přenos několika souborů byste měli svorku Shift + Alt!

Po proceduře budeme mít soubor ve druhém projektu Třída1.cs s příslušnou ikonou (modrá šipka):

Vytváříme jeden projekt pluginu s kompilací pro různé verze Revit / AutoCAD

Při úpravě kódu v okně editoru si také můžete vybrat, v rámci kterého projektu chcete kód zobrazit, což vám umožní vidět upravovaný kód s různými symboly podmíněné kompilace:

Vytváříme jeden projekt pluginu s kompilací pro různé verze Revit / AutoCAD

Podle tohoto schématu vytváříme všechny ostatní projekty (2017-2020). Life hack - pokud přetahujete soubory v průzkumníku řešení nikoli ze základního projektu, ale z projektu, kde jsou již vloženy jako odkaz, nemůžete podržet klávesu Alt!

Popsaná možnost je docela dobrá, dokud nebude přidána nová verze pluginu nebo dokud nebudou přidány nové soubory do projektu - to vše se stává velmi nudným. A nedávno jsem si najednou najednou uvědomil, jak to všechno vyřešit jedním projektem a přecházíme na druhý způsob.

Kouzlo konfigurací

Po přečtení zde můžete zvolat: „Co jsi sakra popsal první metodu, když je článek hned o té druhé?!“. A vše jsem popsal, aby bylo jasnější, proč potřebujeme symboly podmíněné kompilace a v jakých místech se naše projekty liší. A nyní je nám jasnější, jaké rozdíly v projektech musíme implementovat, takže zůstává pouze jeden projekt.

A aby bylo vše zřetelnější, nebudeme vytvářet nový projekt, ale provedeme změny v našem aktuálním projektu vytvořeném prvním způsobem.

Nejprve tedy z řešení odstraníme všechny projekty, kromě toho hlavního (obsahujícího přímo soubory). Tito. projekty pro verze 2016-2020. Otevřete složku s řešením a smažte tam složky těchto projektů.

V řešení nám zbývá jeden projekt - MySuperPluginForRevit_2015. Otevřete jeho vlastnosti a:

  1. Na kartě „Aplikace» odstraňte příponu z názvu sestavy _2015 (později bude jasné proč)
  2. Na kartě „shromáždění»odstraňte symbol podmíněné kompilace R2015 z odpovídajícího pole

Poznámka: V nejnovější verzi sady Visual Studio je chyba – symboly podmíněné kompilace se nezobrazují v okně vlastností projektu, i když jsou k dispozici. Pokud máte tuto závadu, musíte je ručně odstranit ze souboru .csproj. Musíme na tom však ještě zapracovat, tak čtěte dál.

Přejmenujte projekt v okně Solution Explorer odstraněním přípony _2015 a poté odstraňte projekt z řešení. To je nutné pro udržení pořádku a pocitů perfekcionistů! Otevřeme složku našeho řešení, stejným způsobem tam přejmenujeme složku projektu a nahrajeme projekt zpět do řešení.

Otevřete správce konfigurace. US konfigurace Uvolnění v zásadě to nebude potřeba, takže to mažeme. Vytváříme nové konfigurace s názvy, které jsou nám již známé R2015, R2016, ..., R2020. Pamatujte, že nemusíte kopírovat nastavení z jiných konfigurací a nemusíte vytvářet konfigurace projektu:

Vytváříme jeden projekt pluginu s kompilací pro různé verze Revit / AutoCAD

Přejdeme do složky s projektem a otevřeme soubor s příponou .csproj ve vhodném editoru. Mimochodem, můžete ho otevřít i ve Visual Studiu - musíte projekt uvolnit a poté bude v kontextovém menu správná položka:

Vytváříme jeden projekt pluginu s kompilací pro různé verze Revit / AutoCAD

Úpravy v sadě Visual Studio jsou ještě výhodnější, protože editor zarovnává i výzvy.

V souboru uvidíme prvky PropertyGroup - úplně nahoře je generál a pak přijďte s podmínkami. Tyto prvky nastavují vlastnosti projektu při jeho sestavení. První prvek, který je bez podmínek, nastavuje obecné vlastnosti a prvky s podmínkami mění některé vlastnosti v závislosti na konfiguracích.

Přejděte na společný (první) prvek PropertyGroup a podívat se na nemovitost AssemblyName - to je název sestavy a musíme ji mít bez koncovky _2015. Pokud existuje přípona, odstraňte ji.

Nalezení prvku s podmínkou

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

Nepotřebujeme to - smažeme to.

Prvek s podmínkou

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

bude potřeba pro práci ve fázi vývoje a ladění kódu. Jeho vlastnosti můžete změnit podle svých potřeb – nastavit různé výstupní cesty, změnit symboly podmíněné kompilace atd.

Nyní vytváříme nové prvky PropertyGroup pro naše konfigurace. V těchto prvcích stačí nastavit čtyři vlastnosti:

  • Výstupní cesta - výstupní složka. Nastavil jsem výchozí hodnotu binR20xx
  • DefineConstants jsou symboly podmíněné kompilace. Mělo by být nastaveno na TRACE;R20xx
  • TargetFrameworkVersion – verze platformy. Různé verze rozhraní Revit API vyžadují nastavení různých platforem.
  • AssemblyName – název sestavení (tj. název souboru). Můžete napsat přímo požadovaný název sestavy, ale pro univerzálnost vám doporučuji napsat hodnotu $(AssemblyName)_20xx. Za tímto účelem jsme dříve odstranili příponu z názvu sestavení

Nejdůležitější vlastností všech těchto prvků je, že je lze jednoduše zkopírovat do jiných projektů, aniž by se vůbec měnily. Později v článku připojím celý obsah souboru .csproj.

No, přišli jsme na vlastnosti projektu - není to těžké. Co ale dělat s přiloženými knihovnami (balíčky NuGet). Pokud se podíváme dále, uvidíme, že zahrnuté knihovny jsou specifikovány prvky Skupina položek. Ale tady je problém - tento prvek nesprávně zpracovává podmínky jako prvek PropertyGroup. Možná je to dokonce závada sady Visual Studio, ale pokud nastavíte několik prvků Skupina položek s konfiguračními podmínkami a dovnitř vložte různé odkazy na balíčky NuGet, pak když změníte konfiguraci, všechny zadané balíčky se připojí k projektu.

Na pomoc nám přichází živel Vybrat, který funguje podle nám známé logiky if-then-else.

Použití prvku Vybrat, nastavili jsme různé balíčky NuGet pro různé konfigurace:

Veškerý obsah csproj

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0"  ">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>binDebug</OutputPath>
    <DefineConstants>DEBUG;R2015</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'R2015|AnyCPU' ">
    <OutputPath>binR2015</OutputPath>
    <DefineConstants>TRACE;R2015</DefineConstants>
    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
    <AssemblyName>$(AssemblyName)_2015</AssemblyName>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'R2016|AnyCPU' ">
    <OutputPath>binR2016</OutputPath>
    <DefineConstants>TRACE;R2016</DefineConstants>
    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
    <AssemblyName>$(AssemblyName)_2016</AssemblyName>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'R2017|AnyCPU' ">
    <OutputPath>binR2017</OutputPath>
    <DefineConstants>TRACE;R2017</DefineConstants>
    <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
    <AssemblyName>$(AssemblyName)_2017</AssemblyName>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'R2018|AnyCPU' ">
    <OutputPath>binR2018</OutputPath>
    <DefineConstants>TRACE;R2018</DefineConstants>
    <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
    <AssemblyName>$(AssemblyName)_2018</AssemblyName>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'R2019|AnyCPU' ">
    <OutputPath>binR2019</OutputPath>
    <DefineConstants>TRACE;R2019</DefineConstants>
    <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
    <AssemblyName>$(AssemblyName)_2019</AssemblyName>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'R2020|AnyCPU' ">
    <OutputPath>binR2020</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="PropertiesAssemblyInfo.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>

Vezměte prosím na vědomí, že v jedné z podmínek jsem zadal dvě konfigurace přes NEBO. Požadovaný balíček bude tedy připojen během konfigurace Ladit.

A tady jsme téměř dokonalí. Načteme projekt zpět, zahrneme konfiguraci, kterou potřebujeme, zavoláme v kontextové nabídce řešení (nikoli projektu) položku "Obnovte všechny balíčky NuGet„a vidíme, jak se naše balíčky mění.

Vytváříme jeden projekt pluginu s kompilací pro různé verze Revit / AutoCAD

A v této fázi jsem se dostal do slepé uličky - abychom mohli shromáždit všechny konfigurace najednou, mohli jsme použít dávkovou montáž (menu "shromáždění„->“Dávková montáž“), ale při přepínání konfigurací se balíčky automaticky neobnovují. A při sestavování projektu k tomu také nedochází, i když by teoreticky mělo. Nenašel jsem řešení tohoto problému standardními prostředky. A s největší pravděpodobností je to také chyba Visual Studia.

Proto bylo pro dávkovou montáž rozhodnuto použít speciální automatizovaný montážní systém Nuke. Opravdu jsem to nechtěl, protože si myslím, že je to přehnané, pokud jde o vývoj pluginů, ale v tuto chvíli nevidím žádné jiné řešení. A na otázku "Proč Nuke?" Odpověď je jednoduchá – používáme ji v práci.

Přejděte tedy do složky našeho řešení (nikoli projektu), podržte klávesu směna a klikněte pravým tlačítkem myši na prázdné místo ve složce - v kontextové nabídce vyberte položku "Zde otevřete okno PowerShellu".

Vytváříme jeden projekt pluginu s kompilací pro různé verze Revit / AutoCAD

Pokud jste nenainstalovali nuke, pak nejprve napište příkaz

dotnet tool install Nuke.GlobalTool –global

Nyní napište příkaz nuke a budete vyzváni ke konfiguraci nuke pro aktuální projekt. Nevím, jak to správně napsat v ruštině - v angličtině to bude napsáno Nelze najít soubor .nuke. Chcete nastavit sestavení? [y/n]

Stiskněte klávesu Y a poté dojde k přímému nastavení. Chceme nejjednodušší možnost použití MSBuild, takže odpovídáme jako na snímku obrazovky:

Vytváříme jeden projekt pluginu s kompilací pro různé verze Revit / AutoCAD

Pojďme do Visual Studia, které nás vyzve k opětovnému načtení řešení, protože do něj byl přidán nový projekt. Znovu načtěte řešení a uvidíte, že máme projekt stavět ve kterém nás zajímá pouze jeden soubor - Build.cs

Vytváříme jeden projekt pluginu s kompilací pro různé verze Revit / AutoCAD

Otevřeme tento soubor a napíšeme skript pro sestavení projektu pro všechny konfigurace. Nebo použijte můj skript, který si můžete upravit sami:

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"));
            }
        });
}

Vrátíme se do okna PowerShellu a příkaz napíšeme znovu nuke (můžete napsat příkaz nuke s uvedením požadovaného Cíl. Ale my jeden máme Cílkterá se spustí ve výchozím nastavení). Po stisknutí klávesy Enter si budeme připadat jako skuteční hackeři, protože jako ve filmu se náš projekt automaticky sestaví pro různé konfigurace.

Mimochodem, PowerShell můžete používat přímo z Visual Studia (nabídka "Pohled„->“Ostatní okna„->“Konzole správce balíčků“), ale vše bude černobílé, což není příliš výhodné.

Tím můj článek končí. Jsem si jistý, že můžete přijít na možnost pro AutoCAD sami. Doufám, že si zde prezentovaný materiál najde své "klienty".

Спасибо за внимание!

Zdroj: www.habr.com

Přidat komentář