Fem un projecte de complements amb compilació per a diferents versions de Revit / AutoCAD

Fem un projecte de complements amb compilació per a diferents versions de Revit / AutoCAD

Quan es desenvolupen connectors per a aplicacions CAD (En el meu cas es tracta d'AutoCAD, Revit i Renga) amb el pas del temps, apareix un problema: es publiquen noves versions de programes, s'han de fer canvis d'API i noves versions de connectors.

Quan només tens un connector o encara ets un principiant autodidacte en aquesta matèria, només pots fer una còpia del projecte, canviar-hi els llocs necessaris i muntar una nova versió del connector. En conseqüència, els canvis posteriors al codi comportaran un augment múltiple dels costos laborals.

A mesura que adquireixis experiència i coneixements, trobaràs diverses maneres d'automatitzar aquest procés. He fet aquest camí i us vull explicar amb què he acabat i com de convenient és.

En primer lloc, mirem un mètode que és evident i que he utilitzat durant molt de temps.

Enllaços als fitxers del projecte

I perquè tot sigui senzill, visual i entenedor, ho descriuré tot utilitzant un exemple abstracte de desenvolupament de complements.

Obrim Visual Studio (tinc la versió Community 2019. I sí, en rus) i creem una solució nova. Anem a cridar-lo MySuperPluginForRevit

Fem un projecte de complements amb compilació per a diferents versions de Revit / AutoCAD

Farem un complement per a Revit per a les versions 2015-2020. Per tant, creem un nou projecte a la solució (Net Framework Class Library) i l'anomenem MySuperPluginForRevit_2015

Fem un projecte de complements amb compilació per a diferents versions de Revit / AutoCAD

Hem d'afegir enllaços a l'API de Revit. Per descomptat, podem afegir enllaços a fitxers locals (haurem d'instal·lar tots els SDK necessaris o totes les versions de Revit), però de seguida seguirem el camí correcte i connectarem el paquet NuGet. Podeu trobar uns quants paquets, però jo faré servir els meus.

Després de connectar el paquet, feu clic amb el botó dret a l'element "Referències"i seleccioneu l'element"Mou packages.config a PackageReference...»

Fem un projecte de complements amb compilació per a diferents versions de Revit / AutoCAD

Si de sobte en aquest punt comences a entrar en pànic, perquè a la finestra de propietats del paquet no hi haurà cap element important "Copia localment", que definitivament hem d'ajustar al valor false, aleshores no us espanteu: aneu a la carpeta amb el projecte, obriu el fitxer amb l'extensió .csproj en un editor que us convingui (faig servir Notepad++) i hi trobareu una entrada sobre el nostre paquet. Ara es veu així:

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

Afegeix-hi una propietat temps d'execució. Sortirà així:

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

Ara, quan es construeix un projecte, els fitxers del paquet no es copiaran a la carpeta de sortida.
Anem més enllà: imaginem immediatament que el nostre connector utilitzarà alguna cosa de l'API de Revit, que ha canviat amb el temps quan s'han llançat noves versions. Bé, o només hem de canviar alguna cosa al codi en funció de la versió de Revit per a la qual estem fent el connector. Per resoldre aquestes diferències en el codi, utilitzarem símbols de compilació condicional. Obriu les propietats del projecte, aneu a la pestanya “assemblea"i al camp"Notació de compilació condicional"Escrivim R2015.

Fem un projecte de complements amb compilació per a diferents versions de Revit / AutoCAD

Tingueu en compte que el símbol s'ha d'afegir tant per a les configuracions de depuració com per a la versió.

Bé, mentre estem a la finestra de propietats, anem immediatament a la pestanya “Aplicació"i al camp"Espai de noms per defecte» elimina el sufix _2015de manera que el nostre espai de noms sigui universal i independent del nom de l'assemblea:

Fem un projecte de complements amb compilació per a diferents versions de Revit / AutoCAD

En el meu cas, al producte final, els connectors de totes les versions es posen en una carpeta, de manera que els meus noms de conjunt es mantenen amb el sufix del formulari _20хх. Però també podeu eliminar el sufix del nom del conjunt si se suposa que els fitxers es troben en carpetes diferents.

Anem al codi del fitxer Class1.cs i simular algun codi allà, tenint en compte diferents versions de 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;
        }
    }
}

Immediatament vaig tenir en compte totes les versions de Revit anteriors a la versió 2015 (que estaven disponibles en el moment d'escriure) i de seguida vaig tenir en compte la presència de símbols de compilació condicional, que es creen amb la mateixa plantilla.

Passem al més destacat. Creem un nou projecte a la nostra solució, només per a la versió del connector per a Revit 2016. Repetim tots els passos descrits anteriorment, respectivament, substituint el número 2015 pel número 2016. Però el fitxer Class1.cs esborrar del nou projecte.

Fem un projecte de complements amb compilació per a diferents versions de Revit / AutoCAD

Fitxer amb el codi necessari - Class1.cs – ja el tenim i només cal inserir-hi un enllaç en un projecte nou. Hi ha dues maneres d'inserir enllaços:

  1. Llarg - Feu clic amb el botó dret al projecte i seleccioneu "Afegir»->«Element existent", a la finestra que s'obre, cerqueu el fitxer necessari i en lloc de l'opció "Afegir"selecciona l'opció"Afegeix com a connexió»

Fem un projecte de complements amb compilació per a diferents versions de Revit / AutoCAD

  1. Curt - directament a l'explorador de solucions, seleccioneu el fitxer desitjat (o fins i tot fitxers, o fins i tot carpetes senceres) i arrossegueu-lo a un projecte nou mentre manteniu premuda la tecla Alt. Mentre arrossegueu, veureu que quan premeu la tecla Alt, el cursor del ratolí canviarà d'un signe més a una fletxa.
    ACTUALITZACIÓ: Vaig fer una mica de confusió en aquest paràgraf: per transferir diversos fitxers, hauríeu de mantenir premut Maj + Alt!

Després de realitzar el tràmit, tindrem una fitxa en el segon projecte Class1.cs amb la icona corresponent (fletxa blava):

Fem un projecte de complements amb compilació per a diferents versions de Revit / AutoCAD

Quan editeu el codi a la finestra de l'editor, també podeu triar en quin context del projecte voleu mostrar el codi, cosa que us permetrà veure el codi que s'està editant sota diferents símbols de compilació condicional:

Fem un projecte de complements amb compilació per a diferents versions de Revit / AutoCAD

Creem tots els altres projectes (2017-2020) amb aquest esquema. Truc de vida: si arrossegueu fitxers a l'Explorador de solucions no des del projecte base, sinó des del projecte on ja s'han inserit com a enllaç, no haureu de mantenir premuda la tecla Alt!

L'opció descrita és bastant bona fins al moment d'afegir una nova versió del connector o fins al moment d'afegir nous fitxers al projecte; tot això es fa molt tediós. I recentment, de sobte, em vaig adonar de com ordenar-ho tot amb un projecte i estem passant al segon mètode

La màgia de les configuracions

Després d'haver acabat de llegir aquí, podeu exclamar: "Per què vau descriure el primer mètode, si l'article tracta immediatament del segon?!" I ho vaig descriure tot per aclarir per què necessitem símbols de compilació condicionals i en quins llocs difereixen els nostres projectes. I ara ens queda més clar quines diferències en els projectes hem d'implementar, deixant només un projecte.

I per fer-ho tot més evident, no crearem un nou projecte, sinó que farem canvis al nostre projecte actual creat de la primera manera.

Per tant, primer de tot, eliminem tots els projectes de la solució excepte el principal (que conté els fitxers directament). Aquells. projectes per a les versions 2016-2020. Obriu la carpeta amb la solució i suprimiu-hi les carpetes d'aquests projectes.

Ens queda un projecte a la nostra decisió: MySuperPluginForRevit_2015. Obriu les seves propietats i:

  1. A la pestanya "Aplicació"elimineu el sufix del nom del conjunt _2015 (després quedarà clar per què)
  2. A la pestanya "assemblea» elimina el símbol de compilació condicional R2015 del camp corresponent

Nota: la darrera versió de Visual Studio té un error: els símbols de compilació condicional no es mostren a la finestra de propietats del projecte, tot i que estan disponibles. Si experimenteu aquest error, haureu d'eliminar-los manualment del fitxer .csproj. Tanmateix, encara hem de treballar-hi, així que segueix llegint.

Canvieu el nom del projecte a la finestra de l'Explorador de solucions eliminant el sufix _2015 i després elimineu el projecte de la solució. Això és necessari per mantenir l'ordre i els sentiments de perfeccionistes! Obrim la carpeta de la nostra solució, canviem el nom de la carpeta del projecte allà de la mateixa manera i tornem a carregar el projecte a la solució.

Obriu el gestor de configuració. Configuració dels EUA Deixeu anar en principi, no serà necessari, així que l'eliminem. Creem noves configuracions amb noms que ja ens són familiars R2015, R2016, ..., R2020. Tingueu en compte que no cal que copieu la configuració d'altres configuracions i que no cal que creeu configuracions de projecte:

Fem un projecte de complements amb compilació per a diferents versions de Revit / AutoCAD

Aneu a la carpeta amb el projecte i obriu el fitxer amb l'extensió .csproj en un editor que us convingui. Per cert, també podeu obrir-lo a Visual Studio: heu de descarregar el projecte i, a continuació, l'element desitjat estarà al menú contextual:

Fem un projecte de complements amb compilació per a diferents versions de Revit / AutoCAD

L'edició a Visual Studio és fins i tot preferible, ja que l'editor s'alinea i sol·licita.

A l'arxiu veurem els elements PropertyGroup – al capdamunt hi ha la general, i després vénen les condicions. Aquests elements estableixen les propietats del projecte quan es construeix. El primer element, que no té condicions, estableix propietats generals, i els elements amb condicions, en conseqüència, canvien algunes propietats en funció de les configuracions.

Aneu a l'element comú (primer). PropertyGroup i mira la propietat AssemblyName – aquest és el nom de l'assemblea i l'hauríem de tenir sense sufix _2015. Si hi ha un sufix, elimineu-lo.

Trobar un element amb una condició

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

No el necessitem, l'eliminem.

Element amb condició

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

serà necessari per treballar en l'etapa de desenvolupament de codi i depuració. Podeu canviar les seves propietats segons les vostres necessitats: establiu diferents camins de sortida, canvieu els símbols de compilació condicional, etc.

Ara anem a crear nous elements PropertyGroup per a les nostres configuracions. En aquests elements només hem d'establir quatre propietats:

  • OutputPath - Carpeta de sortida. He posat el valor per defecte binR20xx
  • Definir constants – símbols de compilació condicional. S'ha d'especificar el valor TRACE;R20хх
  • TargetFrameworkVersion - versió de plataforma. Les diferents versions de l'API de Revit requereixen especificar diferents plataformes.
  • AssemblyName – nom del conjunt (és a dir, nom del fitxer). Podeu escriure el nom exacte del conjunt, però per versatilitat us recomano escriure el valor $(AssemblyName)_20хх. Per fer-ho, prèviament hem eliminat el sufix del nom del conjunt

La característica més important de tots aquests elements és que simplement es poden copiar en altres projectes sense canviar-los en absolut. Més endavant a l'article adjuntaré tot el contingut del fitxer .csproj.

D'acord, hem descobert les propietats del projecte, no és difícil. Però què fer amb les biblioteques de connectors (paquets NuGet). Si mirem més enllà, veurem que les biblioteques incloses s'especifiquen als elements ItemGroup. Però mala sort: aquest element processa incorrectament les condicions com a element PropertyGroup. Potser això és fins i tot un error de Visual Studio, però si especifiqueu diversos elements ItemGroup amb condicions de configuració i inseriu diferents enllaços als paquets NuGet a l'interior, i quan canvieu la configuració, tots els paquets especificats es connecten al projecte.

L'element ens ajuda Escollir, que funciona segons la nostra lògica habitual si-llavors-altre.

Utilitzant l'element Escollir, establim diferents paquets NuGet per a diferents configuracions:

Tots els continguts 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>

Tingueu en compte que en una de les condicions he especificat dues configuracions mitjançant O. D'aquesta manera, el paquet requerit es connectarà durant la configuració Depurar.

I aquí ho tenim gairebé tot perfecte. Tornem a carregar el projecte, habilitem la configuració que necessitem, anomenem l'element " al menú contextual de la solució (no el projecte)Restaura tots els paquets NuGet"i veiem com canvien els nostres paquets.

Fem un projecte de complements amb compilació per a diferents versions de Revit / AutoCAD

I en aquesta etapa vaig arribar a un carreró sense sortida: per recollir totes les configuracions alhora, podríem utilitzar el muntatge per lots (menú "assemblea»->«Construcció per lots"), però en canviar de configuració, els paquets no es restauren automàticament. I a l'hora de muntar el projecte, això tampoc no passa, tot i que, en teoria, hauria de ser. No he trobat una solució a aquest problema amb mitjans estàndard. I el més probable és que també sigui un error de Visual Studio.

Per tant, per al muntatge per lots, es va decidir utilitzar un sistema especial de muntatge automatitzat Nuke. De fet, no ho volia perquè crec que és excessiu pel que fa al desenvolupament de complements, però de moment no veig cap altra solució. I a la pregunta "Per què Nuke?" La resposta és senzilla: la fem servir a la feina.

Per tant, aneu a la carpeta de la nostra solució (no al projecte), manteniu premuda la tecla Canviar i feu clic amb el botó dret a un espai buit de la carpeta; al menú contextual, seleccioneu l'element "Obriu la finestra de PowerShell aquí».

Fem un projecte de complements amb compilació per a diferents versions de Revit / AutoCAD

Si no el tens instal·lat nuclear, després escriviu primer l'ordre

dotnet tool install Nuke.GlobalTool –global

Ara escriviu l'ordre nuclear i se us demanarà que configureu nuclear per al projecte actual. No sé com escriure això més correctament en rus; en anglès s'escriurà No s'ha pogut trobar el fitxer .nuke. Vols configurar una compilació? [y/n]

Premeu la tecla Y i després hi haurà elements de configuració directes. Necessitem l'opció més senzilla d'utilitzar MSBuild, així que responem com a la captura de pantalla:

Fem un projecte de complements amb compilació per a diferents versions de Revit / AutoCAD

Anem a Visual Studio, que ens demanarà que tornem a carregar la solució, ja que s'hi ha afegit un nou projecte. Tornem a carregar la solució i veiem que tenim un projecte construir en el qual només estem interessats en un fitxer - Build.cs

Fem un projecte de complements amb compilació per a diferents versions de Revit / AutoCAD

Obriu aquest fitxer i escriviu un script per crear el projecte per a totes les configuracions. Bé, o utilitzeu el meu script, que podeu editar segons les vostres necessitats:

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

Tornem a la finestra de PowerShell i tornem a escriure l'ordre nuclear (podeu escriure l'ordre nuclear indicant el requerit Objectiu. Però en tenim un Objectiu, que s'executa per defecte). Després de prémer la tecla Enter, ens sentirem com uns autèntics pirates informàtics, perquè, com en una pel·lícula, el nostre projecte s'assemblarà automàticament per a diferents configuracions.

Per cert, podeu utilitzar PowerShell directament des de Visual Studio (menú "Veure»->«Altres finestres»->«Consola del gestor de paquets"), però tot serà en blanc i negre, cosa que no és gaire convenient.

Això conclou el meu article. Estic segur que podeu esbrinar l'opció per a AutoCAD vosaltres mateixos. Espero que el material aquí presentat trobi els seus "clients".

Gràcies!

Font: www.habr.com

Afegeix comentari