Fazemos um projeto de plugin com compilação para diferentes versões do Revit/AutoCAD

Fazemos um projeto de plugin com compilação para diferentes versões do Revit/AutoCAD

Ao desenvolver plugins para aplicativos CAD (no meu caso são AutoCAD, Revit e Renga) com o tempo surge um problema – novas versões dos programas são lançadas, suas APIs mudam e precisamos criar novas versões dos plugins.

Quando você tem apenas um plugin ou se é um novato autodidata nessa tarefa, pode simplesmente fazer uma cópia do projeto, alterar os pontos necessários e compilar uma nova versão do plugin. Consequentemente, futuras alterações no código resultarão em um aumento significativo no esforço de trabalho.

À medida que você acumula experiência e conhecimento, encontrará algumas maneiras de automatizar esse processo. Eu percorri esse caminho e quero compartilhar com você o que aprendi e o quão conveniente isso se tornou.

Para começar, vamos considerar um método que é óbvio e que eu usei por um longo tempo.

Links para arquivos do projeto

E para que tudo seja simples, claro e fácil de entender, descreverei tudo em um exemplo abstrato de desenvolvimento de plugin.

Vamos abrir o Visual Studio (eu estou usando a versão Community 2019. E sim – em português) e criar uma nova solução. Vamos chamá-la de MySuperPluginForRevit

Fazemos um projeto de plugin com compilação para diferentes versões do Revit/AutoCAD

Vamos desenvolver um plugin para Revit para as versões de 2015 a 2020. Portanto, vou criar um novo projeto na solução (Biblioteca de Classes Net Framework) e Chamá-lo de MySuperPluginForRevit_2015

Fazemos um projeto de plugin com compilação para diferentes versões do Revit/AutoCAD

Precisamos adicionar referências à API do Revit. Claro, podemos adicionar referências a arquivos locais (teremos que instalar todos os SDKs necessários ou todas as versões do Revit), mas vamos seguir o caminho certo e conectar um pacote NuGet. Você pode encontrar uma quantidade considerável de pacotes, mas vou usar os meus próprios.

Depois de conectar o pacote, clique com o botão direito no item «Links» e selecione no menu a opção «Mover packages.config para PackageReference…»

Fazemos um projeto de plugin com compilação para diferentes versões do Revit/AutoCAD

Se de repente você ficar em pânico neste ponto, porque na janela de propriedades do pacote não houver um item importante «Copiar localmente», que precisamos definir como falso, não entre em pânico – vá para a pasta do projeto, abra o arquivo com extensão .csproj em um editor de sua escolha (eu uso o Notepad++) e encontre a entrada para nosso pacote. Ele deve parecer assim:

1.0.0

Adicione a ele a propriedade runtime. Ficará assim:

1.0.0
  runtime

Agora, ao construir o projeto, os arquivos do pacote não serão copiados para a pasta de saída.
Vamos em frente – vamos imaginar que o nosso plugin usará algo da API do Revit, que mudou desde o lançamento de novas versões. Ou simplesmente precisamos mudar algo no código dependendo da versão do Revit para a qual estamos criando o plugin. Para lidar com tais diferenças no código, usamos os símbolos de compilação condicional. Vamos abrir as propriedades do projeto, ir para a aba “Compilação” e no campo “Símbolos de compilação condicional” vamos escrever R2015.

Fazemos um projeto de plugin com compilação para diferentes versões do Revit/AutoCAD

Observe que o símbolo deve ser adicionado tanto para a configuração de Debug quanto para a configuração de Release.

E enquanto estamos na janela de propriedades, vamos para a aba “Aplicativo” e no campo “Espaço de nomes padrão” e vamos remover o sufixo _2015, para que o nosso espaço de nomes seja universal e independente do nome da assembly:

Fazemos um projeto de plugin com compilação para diferentes versões do Revit/AutoCAD

No meu caso, no produto final, os plugins de todas as versões são armazenados em uma única pasta, então os nomes das assemblies permanecem com sufixos do tipo _20xx. Mas você pode remover o sufixo do nome da assembly, se os arquivos forem para pastas diferentes.

Vamos para o código do arquivo Class1.cs e imitar algum código considerando as diferentes versões do 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", "Olá Revit 2015");
#elif R2016
            TaskDialog.Show("ModPlus", "Olá Revit 2016");
#elif R2017
            TaskDialog.Show("ModPlus", "Olá Revit 2017");
#elif R2018
            TaskDialog.Show("ModPlus", "Olá Revit 2018");
#elif R2019
            TaskDialog.Show("ModPlus", "Olá Revit 2019");
#elif R2020
            TaskDialog.Show("ModPlus", "Olá Revit 2020");
#endif
            return Result.Succeeded;
        }
    }
}

Eu considerei imediatamente todas as versões do Revit acima da versão 2015 (que estavam disponíveis no momento da redação do artigo) e levei em conta os símbolos de compilação condicional, que sigo criando com o mesmo padrão.

Vamos para o destaque principal. Criamos um novo projeto em nossa solução, mas agora para a versão do plugin para o Revit 2016. Repetimos todas as etapas descritas acima, substituindo o número 2015 pelo número 2016. Mas o arquivo Class1.cs do novo projeto é removido.

Fazemos um projeto de plugin com compilação para diferentes versões do Revit/AutoCAD

O arquivo com o código necessário – Class1.cs – já temos e precisamos apenas inserir um link para ele no novo projeto. Existem duas formas de inserir links:

  1. Longo – clicamos com o botão direito no projeto, selecionamos a opção «Adicionar» -> «Elemento existente», na janela que se abre, encontramos o arquivo desejado e, em vez da opção «Adicionar» escolhemos a opção «Adicionar como vínculo»

Fazemos um projeto de plugin com compilação para diferentes versões do Revit/AutoCAD

  1. Curto – diretamente no explorador de soluções, escolhemos o arquivo (ou até arquivos. Também podemos até mesmo arrastar pastas inteiras) e arrastamos para o novo projeto mantendo a tecla Alt pressionada. Ao arrastar, você verá que ao pressionar a tecla Alt, o cursor do mouse mudará de um sinal de mais para uma seta.
    UPD: Eu causei um pouco de confusão neste parágrafo — para arrastar vários arquivos, é necessário manter pressionado Shift+Alt!

Após a condução do procedimento, teremos no segundo projeto um arquivo Class1.cs com o ícone correspondente (seta azul):

Fazemos um projeto de plugin com compilação para diferentes versões do Revit/AutoCAD

Ao editar o código na janela do editor, você também pode escolher em qual projeto mostrar o código, o que permite que você veja e edite o código em diferentes condições compiladas:

Fazemos um projeto de plugin com compilação para diferentes versões do Revit/AutoCAD

Seguindo este esquema, criamos todos os outros projetos (2017-2020). Dica — se arrastar arquivos no explorador de soluções não do projeto base, mas de um projeto onde eles já estão inseridos como vínculo, não é necessário manter a tecla Alt pressionada!

A opção descrita é bastante boa até o momento em que uma nova versão do plug-in é adicionada ou novos arquivos são adicionados ao projeto – tudo isso se torna muito complicado. E recentemente eu percebi como resolver tudo isso em um único projeto e estamos passando para o segundo método.

Magia das configurações

Ao ler até aqui, você pode exclamar: «E por que você descreveu o primeiro método, se o artigo é imediatamente sobre o segundo?!». Eu descrevi tudo isso para esclarecer para que precisamos de símbolos de compilação condicional e em quais locais nossos projetos diferem. E agora nos torna mais claro quais diferenças entre os projetos precisamos implementar, deixando apenas um projeto.

E, para que tudo fique mais claro, não criaremos um novo projeto, mas faremos alterações no nosso projeto atual, criado pelo primeiro método.

Então, em primeiro lugar, removemos da solução todos os projetos, exceto o principal (que contém diretamente os arquivos). Ou seja, os projetos para as versões de 2016-2020. Abrimos a pasta da solução e removemos as pastas desses projetos.

Agora na solução, temos apenas um projeto — MySuperPluginForRevit_2015. Abrimos suas propriedades e:

  1. Na aba «Aplicativo» removemos o sufixo do nome da compilação _2015 (em breve ficará claro para que isso é necessário)
  2. Na aba «Compilação» removemos o símbolo de compilação condicional R2015 do campo correspondente

Nota: na versão mais recente do Visual Studio, há um bug – os símbolos de compilação condicional não aparecem na janela de propriedades do projeto, embora estejam presentes. Se você estiver enfrentando esse bug, precisará removê-los manualmente do arquivo .csproj. No entanto, ainda precisamos trabalhar nele, então vamos continuar a leitura.

Renomeamos o projeto na janela do Solution Explorer, removendo o sufixo _2015 e, em seguida, removemos o projeto da solução. Isso é necessário para manter a ordem e a sensibilidade dos perfeccionistas! Abrimos a pasta da nossa solução, renomeamos a pasta do projeto da mesma forma e carregamos o projeto de volta na solução.

Abrimos o Gerenciador de Configurações. A configuração Release não será realmente necessária, então vamos removê-la. Criamos novas configurações com os nomes que já conhecemos R2015, R2016, …, R2020. Note que não é necessário copiar os parâmetros de outras configurações e não é preciso criar configurações de projeto:

Fazemos um projeto de plugin com compilação para diferentes versões do Revit/AutoCAD

Vamos para a pasta do projeto e abrimos o arquivo com a extensão .csproj em um editor que você preferir. Aliás, ele pode ser aberto também no Visual Studio – é necessário descarregar o projeto e, em seguida, haverá a opção necessária no menu de contexto:

Fazemos um projeto de plugin com compilação para diferentes versões do Revit/AutoCAD

Editar no Visual Studio é até preferível, pois o editor alinha e sugere.

No arquivo, veremos os elementos PropertyGroup – na parte superior vai o geral, seguido dos que têm condições. Esses elementos definem as propriedades do projeto durante a compilação. O primeiro elemento, que não tem condições, define propriedades gerais, enquanto os elementos com condições, respectivamente, alteram algumas propriedades dependendo das configurações.

Vamos para o elemento geral (o primeiro) PropertyGroup e olhamos a propriedade AssemblyName – este é o nome da montagem e deve estar sem sufixo _2015. Se houver um sufixo, removemos.

Localizamos o elemento com a condição

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

Não precisamos disso – vamos removê-lo.

O elemento com a condição

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

será necessário para trabalhar na fase de desenvolvimento e depuração do código. Você pode alterar suas propriedades de acordo com suas necessidades – definir diferentes caminhos de saída, mudar os símbolos de compilação condicional, etc.

Agora vamos criar novos elementos PropertyGroup para nossas configurações. Nestes elementos, basta definirmos quatro propriedades:

  • OutputPath – a pasta de saída. Estou definindo o valor padrão binR20xx
  • DefineConstants – caracteres de compilação condicional. Deve-se atribuir um valor TRACE;R20xx
  • TargetFrameworkVersion – versão da plataforma. Para diferentes versões da Revit API, é preciso definir plataformas diferentes.
  • AssemblyName – nome da assembleia (ou seja, o nome do arquivo). Você pode escrever o nome da assembleia necessário, mas para maior compatibilidade, recomendo escrever o valor $(AssemblyName)_20xx. Para isso, anteriormente removemos o sufixo do nome da assembleia

A principal característica de todos esses elementos é que podem ser simplesmente copiados para outros projetos sem nenhuma alteração. Mais adiante no artigo, anexarei todo o conteúdo do arquivo .csproj.

Bem, agora que entendemos as propriedades do projeto – isso não é difícil. Mas o que fazer com as bibliotecas conectadas (pacotes NuGet)? Se olharmos mais adiante, veremos que as bibliotecas conectadas são definidas nos elementos ItemGroup. Mas eis o problema – este elemento processa incorretamente as condições, como o elemento PropertyGroup. Isso pode até ser um bug do Visual Studio, mas se você definir vários elementos ItemGroup com condições de configuração, e inserir diferentes referências a pacotes NuGet dentro, ao mudar a configuração, todos os pacotes indicados serão adicionados ao projeto.

Ajudando-nos, vem o elemento Choose, que funciona com a lógica que já conhecemos if-then-else.

Usando o elemento Choose, definimos diferentes pacotes NuGet para diferentes configurações:

Todo o conteúdo 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
          runtime

Observe que em uma das condições eu indiquei duas configurações através de OU (Or). Dessa forma, o pacote correto será conectado durante a configuração Depuração.

E aqui estamos quase perfeitos. Carregamos de volta o projeto, ativamos a configuração necessária, chamamos no menu de contexto da solução (não do projeto) a opção "Restaurar todos os pacotes NuGet" e vemos como nossos pacotes mudam.

Fazemos um projeto de plugin com compilação para diferentes versões do Revit/AutoCAD

E neste ponto eu cheguei a um impasse – para compilar todas as configurações de uma só vez, poderíamos usar a compilação em massa (menu "Compilação» -> «Compilação em massa"), mas na mudança de configurações não ocorre a restauração automática dos pacotes. E na compilação do projeto também não acontece, embora, em teoria, deveria. Eu realmente não encontrei soluções para esse problema com os recursos padrão. E provavelmente isso também é um bug do Visual Studio.

Portanto, para a compilação em massa, decidimos usar um sistema de construção automatizado Nuke. Na verdade, eu não queria isso, pois considero desnecessário no âmbito do desenvolvimento de plugins, mas, no momento, não vejo outra solução. E a resposta para a pergunta "Por que exatamente o Nuke?" é simples – usamos no trabalho.

Então, vamos para a pasta da nossa solução (não do projeto), seguramos a tecla Shift e clicamos com o botão direito do mouse em um espaço vazio na pasta – no menu de contexto, escolhemos a opção "Abrir janela do PowerShell aqui».

Fazemos um projeto de plugin com compilação para diferentes versões do Revit/AutoCAD

Se você não tiver instalado nuke, primeiro digite o comando

dotnet tool install Nuke.GlobalTool –global

Agora digite o comando nuke e você será solicitado a configurar nuke para o projeto atual. Não sei como escrever isso corretamente em português – em inglês estará escrito Could not find .nuke file. Do you want to setup a build? [y/n]

Pressione a tecla Y e em seguida haverá os itens de configuração. Precisamos da opção mais simples usando MSBuild, portanto, respondemos como na captura de tela:

Fazemos um projeto de plugin com compilação para diferentes versões do Revit/AutoCAD

Vamos para o Visual Studio, que nos pedirá para recarregar a solução, pois um novo projeto foi adicionado a ele. Recarregamos a solução e vemos que temos um projeto build no qual estamos interessados apenas em um arquivo – Build.cs

Fazemos um projeto de plugin com compilação para diferentes versões do Revit/AutoCAD

Abrimos esse arquivo e escrevemos um script para compilar o projeto em todas as configurações. Ou usamos meu script, que você pode editar à sua maneira:

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;

    // Se o nome da solução e o nome do projeto (plugin) são diferentes, indique o nome do projeto (plugin) aqui
    string PluginName => Solution.Name;

    Target Compile => _ => _
        .Executes(() =>
        {
            var project = Solution.GetProject(PluginName);
            if (project == null)
                throw new FileNotFoundException("Não encontrado!");

            var build = new List();
            foreach (var (_, c) in project.Configurations)
            {
                var configuration = c.Split("|")[0];

                if (configuration == "Debug" || build.Contains(configuration))
                    continue;

                Logger.Normal($"Configuração: {configuration}");

                build.Add(configuration);

                MSBuild(_ => _
                    .SetProjectFile(project.Path)
                    .SetConfiguration(configuration)
                    .SetTargets("Restore"));
                MSBuild(_ => _
                    .SetProjectFile(project.Path)
                    .SetConfiguration(configuration)
                    .SetTargets("Rebuild"));
            }
        });
}

Voltamos para a janela do PowerShell e digitamos o comando novamente nuke (pode-se digitar o comando nuke especificando o necessário Alvo. Mas temos um Alvo, que é executado por padrão). Após pressionar a tecla Enter, nos sentiremos como verdadeiros hackers, pois, como no filme, ocorrerá a compilação automática do nosso projeto para diferentes configurações.

Aliás, é possível usar o PowerShell direto do Visual Studio (menu "Exibir» -> «Outras janelas» -> «Console do Gerenciador de Pacotes"), mas lá tudo será em preto e branco, o que não é muito conveniente.

Com isso, meu artigo chega ao fim. Tenho certeza de que, com a opção para AutoCAD, você conseguirá lidar sozinho. Espero que o material apresentado aqui encontre seus "clientes".

Obrigado pela atenção!

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster