Facemos un proxecto de complemento con compilación para diferentes versións de Revit/AutoCAD

Facemos un proxecto de complemento con compilación para diferentes versións de Revit/AutoCAD

Ao desenvolver complementos para aplicacións CAD (no meu caso estes son AutoCAD, Revit e Renga) co paso do tempo, aparece un problema: lánzanse novas versións dos programas, hai que facer cambios na API e novas versións dos complementos.

Cando só tes un complemento ou aínda es un principiante autodidacta nesta materia, podes simplemente facer unha copia do proxecto, cambiar os lugares necesarios nel e montar unha nova versión do complemento. En consecuencia, as modificacións posteriores do código suporán un aumento múltiple dos custos laborais.

A medida que adquira experiencia e coñecemento, atopará varias formas de automatizar este proceso. Percorrín este camiño e quero contarvos o que acabei e o cómodo que é.

En primeiro lugar, vexamos un método que é obvio e que usei durante moito tempo.

Ligazóns a ficheiros do proxecto

E para que todo sexa sinxelo, visual e comprensible, describirei todo usando un exemplo abstracto de desenvolvemento de complementos.

Abramos Visual Studio (teño a versión Community 2019. E si, en ruso) e creemos unha nova solución. Imos chamalo MySuperPluginForRevit

Facemos un proxecto de complemento con compilación para diferentes versións de Revit/AutoCAD

Faremos un complemento para Revit para as versións 2015-2020. Polo tanto, imos crear un novo proxecto na solución (Net Framework Class Library) e chamalo MySuperPluginForRevit_2015

Facemos un proxecto de complemento con compilación para diferentes versións de Revit/AutoCAD

Necesitamos engadir ligazóns á API de Revit. Por suposto, podemos engadir ligazóns a ficheiros locais (teremos que instalar todos os SDK necesarios ou todas as versións de Revit), pero seguiremos inmediatamente o camiño correcto e conectaremos o paquete NuGet. Podes atopar bastantes paquetes, pero vou usar o meu.

Despois de conectar o paquete, fai clic co botón dereito sobre o elemento "referencias"e selecciona o elemento"Mover packages.config a PackageReference...»

Facemos un proxecto de complemento con compilación para diferentes versións de Revit/AutoCAD

Se de súpeto neste momento comezas a entrar en pánico, porque na xanela das propiedades do paquete non haberá ningún elemento importante "Copiar localmente", que definitivamente necesitamos establecer o valor teito, entón non te asustes: vai ao cartafol co proxecto, abre o ficheiro coa extensión .csproj nun editor conveniente para ti (uso Notepad++) e atopa alí unha entrada sobre o noso paquete. Ela agora ten este aspecto:

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

Engade unha propiedade a ela tempo de execución. Resultará así:

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

Agora, ao construír un proxecto, os ficheiros do paquete non se copiarán no cartafol de saída.
Imos máis aló: imaxinemos inmediatamente que o noso complemento usará algo da API de Revit, que cambiou co paso do tempo cando se lanzaron novas versións. Ben, ou só necesitamos cambiar algo no código dependendo da versión de Revit para a que esteamos a facer o complemento. Para resolver tales diferenzas no código, utilizaremos símbolos de compilación condicional. Abra as propiedades do proxecto, vai á pestana "Asemblea"e no campo"Notación de compilación condicional"escribamos R2015.

Facemos un proxecto de complemento con compilación para diferentes versións de Revit/AutoCAD

Teña en conta que o símbolo debe engadirse tanto para a configuración de depuración como para a versión.

Ben, mentres estamos na xanela de propiedades, imos inmediatamente á pestana "App"e no campo"Espazo de nomes predeterminado» eliminar o sufixo _2015para que o noso espazo de nomes sexa universal e independente do nome da asemblea:

Facemos un proxecto de complemento con compilación para diferentes versións de Revit/AutoCAD

No meu caso, no produto final, os complementos de todas as versións colócanse nun cartafol, polo que os nomes dos meus conxuntos permanecen co sufixo do formulario _20хх. Pero tamén pode eliminar o sufixo do nome da montaxe se se supón que os ficheiros están situados en cartafoles diferentes.

Imos ao código do ficheiro Clase 1.cs e simula algún código alí, tendo en conta as diferentes versións 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;
        }
    }
}

Inmediatamente tiven en conta todas as versións de Revit anteriores á versión 2015 (que estaban dispoñibles no momento da escritura) e inmediatamente tiven en conta a presenza de símbolos de compilación condicional, que se crean usando o mesmo modelo.

Pasemos ao máis destacado. Creamos un novo proxecto na nosa solución, só para a versión do complemento para Revit 2016. Repetimos todos os pasos descritos anteriormente, respectivamente, substituíndo o número 2015 polo número 2016. Pero o ficheiro Clase 1.cs eliminar do novo proxecto.

Facemos un proxecto de complemento con compilación para diferentes versións de Revit/AutoCAD

Arquivo co código necesario - Clase 1.cs – xa o temos e só necesitamos inserir unha ligazón a el nun novo proxecto. Hai dúas formas de inserir ligazóns:

  1. Longo - Fai clic co botón dereito sobre o proxecto e selecciona "Agregar"->"Elemento existente", na xanela que se abre, busque o ficheiro necesario e en lugar da opción "Agregar"seleccione a opción"Engadir como conexión»

Facemos un proxecto de complemento con compilación para diferentes versións de Revit/AutoCAD

  1. Curto – directamente no explorador de solucións, seleccione o ficheiro desexado (ou mesmo ficheiros, ou mesmo cartafoles enteiros) e arrástreo a un novo proxecto mentres manteña premida a tecla Alt. Ao arrastrar, verá que ao premer a tecla Alt, o cursor do rato pasará dun signo máis a unha frecha.
    ACTUALIZACIÓN: Fixen unha pequena confusión neste parágrafo: para transferir varios ficheiros, debes manter premido Maiús + Alt!

Despois de realizar o trámite, teremos un expediente no segundo proxecto Clase 1.cs coa icona correspondente (frecha azul):

Facemos un proxecto de complemento con compilación para diferentes versións de Revit/AutoCAD

Ao editar o código na xanela do editor, tamén podes escoller en que contexto do proxecto queres mostrar o código, o que che permitirá ver o código que se edita baixo diferentes símbolos de compilación condicional:

Facemos un proxecto de complemento con compilación para diferentes versións de Revit/AutoCAD

Creamos todos os demais proxectos (2017-2020) usando este esquema. Truco de vida: se arrastras ficheiros no Explorador de solucións non desde o proxecto base, senón desde o proxecto onde xa están inseridos como ligazón, non tes que manter premida a tecla Alt.

A opción descrita é bastante boa ata o momento de engadir unha nova versión do complemento ou ata o momento de engadir novos ficheiros ao proxecto; todo isto faise moi tedioso. E recentemente, de súpeto, decateime de como resolvelo todo cun proxecto e estamos pasando ao segundo método

A maxia das configuracións

Despois de terminar de ler aquí, podes exclamar: "Por que describiches o primeiro método, se o artigo trata inmediatamente sobre o segundo?!" E describín todo para que quede máis claro por que necesitamos símbolos de compilación condicional e en que lugares difieren os nosos proxectos. E agora queda máis claro para nós exactamente que diferenzas nos proxectos debemos implementar, quedando só un proxecto.

E para que todo sexa máis evidente, non imos crear un novo proxecto, senón que faremos cambios no noso proxecto actual creado de primeira maneira.

Entón, en primeiro lugar, eliminamos todos os proxectos da solución excepto o principal (que contén os ficheiros directamente). Eses. proxectos para as versións 2016-2020. Abre o cartafol coa solución e elimina alí os cartafoles destes proxectos.

Quédanos un proxecto na nosa decisión: MySuperPluginForRevit_2015. Abre as súas propiedades e:

  1. Na pestana "App"elimina o sufixo do nome da asemblea _2015 (máis tarde quedará claro por que)
  2. Na pestana "Asemblea» eliminar o símbolo de compilación condicional R2015 do campo correspondente

Nota: a última versión de Visual Studio ten un erro: os símbolos de compilación condicional non se amosan na xanela de propiedades do proxecto, aínda que están dispoñibles. Se experimentas este fallo, debes eliminalos manualmente do ficheiro .csproj. Non obstante, aínda temos que traballar nel, así que segue lendo.

Cambia o nome do proxecto na xanela do Explorador de solucións eliminando o sufixo _2015 e, a continuación, elimine o proxecto da solución. Isto é necesario para manter a orde e os sentimentos dos perfeccionistas! Abrimos o cartafol da nosa solución, renomeamos alí o cartafol do proxecto do mesmo xeito e cargamos o proxecto de novo na solución.

Abre o xestor de configuración. configuración dos EUA Solte en principio, non será necesario, polo que o borramos. Creamos novas configuracións con nomes que xa nos son familiares R2015, R2016, ..., R2020. Teña en conta que non precisa copiar a configuración doutras configuracións e non precisa crear configuracións do proxecto:

Facemos un proxecto de complemento con compilación para diferentes versións de Revit/AutoCAD

Vaia ao cartafol co proxecto e abra o ficheiro coa extensión .csproj nun editor conveniente para vostede. Por certo, tamén podes abrilo en Visual Studio: debes descargar o proxecto e, a continuación, o elemento desexado estará no menú contextual:

Facemos un proxecto de complemento con compilación para diferentes versións de Revit/AutoCAD

A edición en Visual Studio é incluso preferible, xa que o editor aliña e solicita.

No ficheiro veremos os elementos PropertyGroup - na parte superior está o xeral, e despois veñen as condicións. Estes elementos definen as propiedades do proxecto cando se constrúe. O primeiro elemento, que é sen condicións, establece propiedades xerais e os elementos con condicións, polo tanto, cambian algunhas propiedades dependendo das configuracións.

Vaia ao elemento común (primeiro). PropertyGroup e mira a propiedade Nome da Asemblea – este é o nome da asemblea e deberiamos telo sen sufixo _2015. Se hai un sufixo, elimínao.

Buscar un elemento cunha condición

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

Non o necesitamos, borrámolo.

Elemento con condición

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

serán necesarios para traballar na fase de desenvolvemento e depuración do código. Podes cambiar as súas propiedades segundo as túas necesidades: establecer diferentes camiños de saída, cambiar os símbolos de compilación condicional, etc.

Agora imos crear novos elementos PropertyGroup para as nosas configuracións. Nestes elementos só necesitamos establecer catro propiedades:

  • Ruta de saída - cartafol de saída. Definei o valor predeterminado binR20xx
  • Definir constantes – Símbolos de compilación condicional. O valor debe ser especificado TRACE;R20хх
  • TargetFrameworkVersion - Versión da plataforma. As diferentes versións da API de Revit requiren que se especifiquen diferentes plataformas.
  • Nome da Asemblea – nome do conxunto (é dicir, o nome do ficheiro). Podes escribir o nome exacto da montaxe, pero para a súa versatilidade recomendo escribir o valor $(AssemblyName)_20хх. Para iso, eliminamos previamente o sufixo do nome da montaxe

A característica máis importante de todos estes elementos é que simplemente se poden copiar noutros proxectos sen cambialos en absoluto. Máis adiante no artigo anexarei todo o contido do ficheiro .csproj.

Está ben, descubrimos as propiedades do proxecto, non é difícil. Pero que facer coas bibliotecas de complementos (paquetes NuGet). Se miramos máis aló, veremos que as bibliotecas incluídas están especificadas nos elementos Grupo de elementos. Pero mala sorte: este elemento procesa incorrectamente as condicións como elemento PropertyGroup. Quizais este sexa incluso un fallo de Visual Studio, pero se especificas varios elementos Grupo de elementos coas condicións de configuración e insira diferentes ligazóns a paquetes NuGet dentro, entón cando cambie a configuración, todos os paquetes especificados están conectados ao proxecto.

O elemento vén na nosa axuda Escoller, que funciona segundo a nosa lóxica habitual se-entón-outro.

Usando elemento Escoller, establecemos diferentes paquetes NuGet para diferentes configuracións:

Todos os contidos 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>

Teña en conta que nunha das condicións especifiquei dúas configuracións mediante OU. Deste xeito, o paquete necesario conectarase durante a configuración Depurar.

E aquí temos case todo perfecto. Volvemos cargar o proxecto, activamos a configuración que necesitamos, chamamos ao elemento " no menú contextual da solución (non o proxecto)Restaura todos os paquetes NuGet"e vemos como cambian os nosos paquetes.

Facemos un proxecto de complemento con compilación para diferentes versións de Revit/AutoCAD

E nesta fase cheguei a un camiño sen saída: para recoller todas as configuracións á vez, poderiamos usar o conxunto de lotes (menú "Asemblea"->"Construción por lotes"), pero ao cambiar de configuración, os paquetes non se restauran automaticamente. E á hora de montar o proxecto, isto tampouco sucede, aínda que, en teoría, debería. Non atopei unha solución a este problema utilizando medios estándar. E o máis probable é que este sexa tamén un erro de Visual Studio.

Polo tanto, para a montaxe por lotes, decidiuse utilizar un sistema de montaxe automatizado especial Nuke. En realidade, non quería isto porque creo que é excesivo en termos de desenvolvemento de complementos, pero polo momento non vexo ningunha outra solución. E á pregunta "Por que Nuke?" A resposta é sinxela: usámolo no traballo.

Entón, vai ao cartafol da nosa solución (non ao proxecto), manteña premida a tecla Desprazarse e fai clic co botón dereito nun espazo baleiro do cartafol; no menú contextual, selecciona o elemento "Abre aquí a xanela de PowerShell».

Facemos un proxecto de complemento con compilación para diferentes versións de Revit/AutoCAD

Se non o tes instalado nuclear, a continuación, escriba primeiro o comando

dotnet tool install Nuke.GlobalTool –global

Agora escribe o comando nuclear e pediráselle que configure nuclear para o proxecto actual. Non sei como escribir isto máis correctamente en ruso; en inglés escribirase. Non se puido atopar o ficheiro .nuke. Queres configurar unha compilación? [i/n]

Preme a tecla Y e despois haberá elementos de configuración directa. Necesitamos a opción máis sinxela de usar MSBuild, así que respondemos como na captura de pantalla:

Facemos un proxecto de complemento con compilación para diferentes versións de Revit/AutoCAD

Imos a Visual Studio, que nos pedirá que recarguemos a solución, xa que se lle engadiu un novo proxecto. Recargamos a solución e vemos que temos un proxecto construír no que só nos interesa un ficheiro - Construír.cs

Facemos un proxecto de complemento con compilación para diferentes versións de Revit/AutoCAD

Abre este ficheiro e escribe un script para construír o proxecto para todas as configuracións. Ben, ou usa o meu script, que podes editar segundo as túas necesidades:

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

Volvemos á xanela de PowerShell e escribimos o comando de novo nuclear (podes escribir o comando nuclear indicando o requirido Branco. Pero temos un Branco, que se executa por defecto). Despois de premer a tecla Intro, sentirémonos como auténticos hackers, xa que, como nunha película, o noso proxecto ensamblarase automaticamente para diferentes configuracións.

Por certo, pode usar PowerShell directamente desde Visual Studio (menú "Ver"->"Outras fiestras"->"Consola do xestor de paquetes"), pero todo será en branco e negro, o que non é moi cómodo.

Isto conclúe o meu artigo. Estou seguro de que pode descubrir a opción de AutoCAD vostede mesmo. Espero que o material aquí presentado atope os seus "clientes".

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

Fonte: www.habr.com

Engadir un comentario