Nous faisons un projet de plugin avec compilation pour différentes versions de Revit / AutoCAD



Lors du développement de plugins pour des applications de CAO ( dans mon cas, AutoCAD, Revit et Renga), un problème se pose au fil du temps - de nouvelles versions de programmes sortent, leurs modifications d'API et de nouvelles versions de plugins doivent être apportées.


Lorsque vous n'avez qu'un seul plugin ou que vous êtes encore un débutant autodidacte dans ce domaine, vous pouvez simplement faire une copie du projet, changer les endroits nécessaires et créer une nouvelle version du plugin. Par conséquent, les modifications ultérieures du code entraîneront une augmentation multiple des coûts de main-d'œuvre.


Au fur et à mesure que vous acquérez de l'expérience et des connaissances, vous trouverez plusieurs façons d'automatiser ce processus. Je suis allé de cette façon et je veux vous dire à quoi je suis finalement arrivé et à quel point c'est pratique.


Pour commencer, pensez à une méthode qui est évidente et que j'utilise depuis longtemps


Liens vers les fichiers de projet


Et pour rendre tout simple, clair et compréhensible, je vais tout décrire en utilisant un exemple abstrait de développement de plug-in.


Ouvrez Visual Studio (j'ai la version Community 2019. Et oui - en russe) et créez une nouvelle solution. Appelons ça MySuperPluginForRevit


image1

Nous allons créer un plugin pour Revit pour les versions 2015-2020. Par conséquent, nous allons créer un nouveau projet dans la solution (bibliothèque de classes Net Framework) et l'appeler MySuperPluginForRevit_2015


image2

Nous devons ajouter des liens vers l'API Revit. Bien sûr, nous pouvons ajouter des liens vers des fichiers locaux (vous devrez installer tous les SDK nécessaires ou toutes les versions de Revit), mais nous irons directement sur le bon chemin et connectons le package NuGet. Vous ne pouvez pas trouver un petit nombre de packages, mais j'utiliserai le mien.


Après avoir connecté le package, faites un clic droit sur l'élément " Liens " et sélectionnez " Transférer packages.config vers PackageReference ... " dans le menu


image3

Si vous paniquez soudainement à ce stade, car la fenêtre des propriétés du package ne contient pas l'élément important " Copier localement ", que nous devons définitivement définir sur faux , alors nous ne devrions pas paniquer - nous allons dans le dossier du projet et ouvrons le fichier avec l'extension. csproj dans un éditeur pratique (j'utilise Notepad ++) et on y trouve un enregistrement sur notre package. Il ressemble à ceci maintenant:


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

Ajoutez-y la propriété <ExcludeAssets> runtime </ExcludeAssets> . Il se présente comme ceci:


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

Désormais, lors de la construction d'un projet, les fichiers du package ne seront pas copiés dans le dossier de sortie.
Nous allons plus loin - imaginez immédiatement que notre plugin utilisera quelque chose de l'API Revit, qui a changé avec la sortie de nouvelles versions. Eh bien, ou tout simplement, nous devons changer quelque chose dans le code, selon la version de Revit pour laquelle nous faisons le plugin. Pour résoudre de telles différences dans le code, nous utiliserons des symboles de compilation conditionnelle. Ouvrez les propriétés du projet, allez dans l'onglet " Assemblage " et dans le champ " Symboles de compilation conditionnelle " écrivez R2015 .


image4

Notez que le symbole doit être ajouté à la fois pour la configuration de débogage et la configuration de version.


Eh bien, pendant que nous sommes dans la fenêtre des propriétés, nous allons immédiatement dans l'onglet « Application » et supprimons le suffixe _2015 dans le champ « Espace de noms par défaut » afin que notre espace de noms soit universel et indépendant du nom de l'assembly:


image5

Dans mon cas, dans le produit final, les plugins de toutes les versions sont placés dans un dossier, donc mes noms d'assembly restent avec le suffixe du formulaire _20xx . Mais vous pouvez également supprimer le suffixe du nom de l'assembly si l'emplacement des fichiers dans différents dossiers est supposé.


Nous passons au code du fichier Class1.cs et y simulons du code, en tenant compte des différentes 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; } } } 

J'ai immédiatement pris en compte toutes les versions de Revit au-dessus de la version 2015 (qui étaient au moment de la rédaction) et immédiatement pris en compte la présence de symboles de compilation conditionnelle, que je crée en utilisant le même modèle.


Nous passons au point culminant principal. Nous créons un nouveau projet dans notre solution, uniquement pour la version plug-in pour Revit 2016. Nous répétons toutes les étapes décrites ci-dessus, respectivement, en remplaçant le numéro 2015 par le numéro 2016. Mais nous supprimons le fichier Class1.cs du nouveau projet.


image6

Le fichier avec le code souhaité - Class1.cs - nous l'avons déjà et nous avons juste besoin d'insérer un lien vers celui-ci dans le nouveau projet. Il existe deux façons d'insérer des liens:


  1. Long - cliquez sur le projet avec le bouton droit de la souris, sélectionnez l'élément " Ajouter " -> " Élément existant ", dans la fenêtre qui s'ouvre, recherchez le fichier souhaité et au lieu de l'option " Ajouter " sélectionnez l'option " Ajouter comme lien "

image7

  1. Court - droit dans l'explorateur de solutions, sélectionnez le fichier souhaité (ou même des fichiers. Ou même des dossiers entiers) et faites glisser et déposez dans le nouveau projet avec la touche Alt enfoncée. Lors du glissement, vous verrez que lorsque vous appuyez sur la touche Alt, le curseur de la souris passera d'un signe plus à une flèche.
    UPD: J'ai fait un peu de confusion dans cette section - pour transférer plusieurs fichiers, maintenez Shift + Alt !

Après la procédure, nous verrons dans le deuxième projet le fichier Class1.cs avec l'icône correspondante (flèche bleue):


image8

Lors de l'édition du code dans la fenêtre de l'éditeur, vous pouvez également choisir dans le contexte de quel projet afficher le code, ce qui vous permet de voir le code en cours d'édition avec différents symboles de compilation conditionnelle:


image9

Dans le cadre de ce programme, nous créons tous les autres projets (2017-2020). Life hack - si vous faites glisser et déposez des fichiers dans l'explorateur de solutions non pas depuis le projet de base, mais depuis le projet où ils sont déjà insérés en tant que lien, vous ne pouvez pas maintenir la touche Alt enfoncée!


L'option décrite est assez bonne jusqu'au moment où la nouvelle version du plugin est ajoutée ou jusqu'à ce que les nouveaux fichiers soient ajoutés au projet - tout cela devient très morne. Et récemment, j'ai soudainement réalisé comment résoudre tout cela avec un seul projet et nous passons à la deuxième méthode


Magie de configuration


Après avoir lu ici, vous pouvez vous exclamer: "Qu'est-ce que tu as décrit la première méthode, si l'article concerne immédiatement la seconde?!" Et j'ai tout décrit pour expliquer pourquoi nous avons besoin de symboles de compilation conditionnelle et à quels endroits nos projets diffèrent. Et maintenant, il devient plus clair pour nous quelles différences de projet spécifiques nous devons mettre en œuvre, ne laissant qu'un seul projet.


Et pour que tout soit plus évident, nous ne créerons pas un nouveau projet, mais apporterons des modifications à notre projet actuel, créé de la première manière.


Donc, tout d'abord, nous supprimons tous les projets de la solution, à l'exception du principal (contenant directement les fichiers). C'est-à-dire projets pour les versions 2016-2020. Ouvrez le dossier de solution et supprimez-y les dossiers de ces projets.


Il nous reste une solution dans la solution - MySuperPluginForRevit_2015 . Nous ouvrons ses propriétés et:


  1. Dans l'onglet Application , supprimez le suffixe _2015 du nom de l'assembly (il deviendra clair pourquoi)
  2. Dans l'onglet « Assemblage », supprimez le symbole de compilation conditionnelle R2015 du champ correspondant

Remarque: dans la dernière version de Visual Studio, il y a un problème - les symboles de compilation conditionnelle ne sont pas affichés dans la fenêtre des propriétés du projet, bien qu'ils existent. Si vous rencontrez ce problème, vous devez les supprimer manuellement du fichier .csproj. Cependant, nous y travaillons toujours, alors lisez la suite.

Renommez le projet dans la fenêtre de l' explorateur de solutions en supprimant le suffixe _2015 , puis supprimez le projet de la solution. Cela est nécessaire pour maintenir l'ordre et les sentiments des perfectionnistes! Nous ouvrons le dossier de notre solution, y renommons le dossier du projet de la même manière et chargeons le projet dans la solution.


Ouvrez le gestionnaire de configuration. En principe, nous n'aurons pas besoin de la configuration Release , nous la supprimons donc. Nous créons de nouvelles configurations avec les noms R2015 , R2016 , ..., R2020 qui nous sont déjà familiers. Veuillez noter que vous n'avez pas besoin de copier les paramètres d'autres configurations et que vous n'avez pas besoin de créer des configurations de projet:


image10

Nous allons dans le dossier du projet et ouvrons le fichier avec l'extension .csproj dans un éditeur pratique. Soit dit en passant, il peut également être ouvert dans Visual Studio - vous devez décharger le projet, puis le bon élément sera dans le menu contextuel:


image11

L'édition dans Visual Studio est même préférable, car l'éditeur à la fois aligne et invite.


Dans le fichier, nous verrons les éléments PropertyGroup - tout en haut est le général, puis avec les conditions. Ces éléments définissent les propriétés du projet lors de son assemblage. Le premier élément, qui sans conditions, définit les propriétés générales et les éléments avec conditions, respectivement, modifient certaines propriétés en fonction des configurations.


Nous allons à l'élément général (premier) du PropertyGroup et regardons la propriété AssemblyName - c'est le nom de l'assembly et nous devrions l'avoir sans le suffixe _2015 . S'il y a un suffixe, supprimez-le.


On trouve un élément avec la condition


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

Nous n'en avons pas besoin - nous le supprimons.


Clause de condition


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

Il sera nécessaire de travailler au stade de développement et de débogage du code. Vous pouvez modifier ses propriétés selon vos besoins - définir différents chemins de sortie, modifier les symboles de compilation conditionnelle, etc.


Créez maintenant de nouveaux éléments PropertyGroup pour nos configurations. Dans ces éléments, nous avons juste besoin de définir quatre propriétés:


  • OutputPath est le dossier de sortie. J'ai défini la valeur standard bin \ R20xx
  • DefineConstants - symboles de compilation conditionnelle. Définissez TRACE; R20xx
  • TargetFrameworkVersion - version de la plateforme. Pour différentes versions de l'API Revit, différentes plates-formes doivent être définies.
  • AssemblyName - nom de l'assembly (c'est-à-dire le nom du fichier). Vous pouvez écrire directement le nom d'assembly souhaité, mais pour l'universalité, je recommande d'écrire la valeur de $ (AssemblyName) _20xx . Pour ce faire, nous avons précédemment supprimé le suffixe du nom de l'assembly

La caractéristique la plus importante de tous ces éléments est qu'ils peuvent être copiés trivialement dans d'autres projets sans les changer du tout. Plus loin dans l'article, je joindrai tout le contenu du fichier .csproj.


Eh bien, nous avons déterminé les propriétés du projet - ce n'est pas difficile. Mais que faire des bibliothèques de plug-ins (packages NuGet). Si nous regardons plus loin, nous verrons que les bibliothèques de plug-ins sont définies par des éléments ItemGroup . Mais voici la malchance - cet élément traite incorrectement les conditions, comme un élément PropertyGroup . Il s'agit peut-être même d'un problème de Visual Studio, mais si vous définissez plusieurs éléments ItemGroup avec des conditions de configuration et insérez différents liens vers les packages NuGet à l'intérieur, puis lorsque vous modifiez la configuration, tous les packages spécifiés sont connectés au projet.


L'élément Choose vient à notre aide, qui fonctionne selon la logique habituelle if-then-else .


En utilisant l'élément Choose , nous définissons différents packages NuGet pour différentes configurations:


Tout le contenu de 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> 

Veuillez noter que dans l'une des conditions, j'ai spécifié deux configurations via OR (ou) . Ainsi, le package nécessaire sera connecté lors de la configuration du débogage .


Et ici, nous avons presque tout est parfait. Nous chargeons le projet, activons la configuration dont nous avons besoin, appelons l'élément « Restaurer tous les packages NuGet » dans le menu contextuel de la solution (pas le projet) et voyons comment les packages changent.


image12

Et à ce stade, je me suis arrêté - afin de collecter toutes les configurations à la fois, nous pourrions utiliser l'assemblage par lots (menu " Assemblage " -> " Assemblage par lots "), mais lors du changement de configuration, les packages ne sont pas automatiquement restaurés. Et lors de l'assemblage du projet, cela ne se produit pas non plus, même si, en théorie, cela devrait se produire. Je n'ai pas trouvé de solution à ce problème par des moyens standard. Et il s'agit très probablement d'un bogue de Visual Studio.


Par conséquent, pour l'assemblage par lots, il a été décidé d'utiliser un système d'assemblage automatisé spécial Nuke . En fait, je ne le voulais pas, car je le considère inutile dans le cadre du développement de plug-ins, mais pour le moment je ne vois pas d'autre solution. Et la question «Pourquoi exactement Nuke?» La réponse est simple - nous l'utilisons au travail.


Donc, nous allons dans le dossier de notre solution (pas le projet), maintenez la touche Maj enfoncée et cliquez avec le bouton droit sur un emplacement vide dans le dossier - dans le menu contextuel, sélectionnez l'élément " Ouvrir la fenêtre PowerShell ici ".


image13

Si vous n'avez pas installé Nuke , écrivez d'abord la commande


 dotnet tool install Nuke.GlobalTool –global 

Maintenant, écrivez la commande nuke et il vous sera demandé de configurer nuke pour le projet en cours. Je ne sais pas comment l'écrire plus correctement en russe - Impossible de trouver le fichier .nuke sera écrit en anglais. Voulez-vous configurer une build? [o / n]


Appuyez sur la touche Y, puis il y aura les paramètres immédiats. Nous avons besoin de l'option la plus simple à l'aide de MSBuild , nous répondons donc comme dans la capture d'écran:


image14

Passons à Visual Studio, qui nous proposera de recharger la solution, puisqu'un nouveau projet y a été ajouté. Nous redémarrons la solution et voyons que nous avons un projet de build dans lequel nous ne sommes intéressés que par un seul fichier - Build.cs


image15

Ouvrez ce fichier et écrivez un script pour générer le projet pour toutes les configurations. Eh bien, ou utilisez mon script, que vous pouvez modifier vous-même:


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

Nous revenons à la fenêtre PowerShell et écrivons à nouveau la commande nuke (vous pouvez écrire la commande nuke avec la cible souhaitée. Mais nous avons une cible , qui démarre par défaut). Après avoir appuyé sur la touche Entrée, nous nous sentirons comme de vrais hackers, car comme dans un film, notre projet sera automatiquement assemblé pour différentes configurations.


Soit dit en passant, vous pouvez utiliser PowerShell directement à partir de Visual Studio (menu " Affichage " -> " Autres fenêtres " -> " Console du gestionnaire de packages "), mais tout sera en noir et blanc, ce qui n'est pas très pratique.


Sur ce mon article est terminé. Je suis sûr que vous pouvez trouver vous-même l'option pour AutoCAD. J'espère que le matériel présenté ici trouvera ses "clients".


Merci de votre attention!

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


All Articles