We create a single plugin project compiled for different versions of Revit/AutoCAD

We create a single plugin project compiled for different versions of Revit/AutoCAD

When developing plugins for CAD applications (in my case this is AutoCAD, Revit, and Renga) over time, one problem arises – new versions of the software are released, their APIs change, and new versions of the plugins need to be made.

When you have only one plugin or you're still a novice in this field, you can simply make a copy of the project, make the necessary changes, and compile a new version of the plugin. Accordingly, subsequent code modifications will lead to a significant increase in labor costs.

As you gain experience and knowledge, you will find several ways to automate this process. I've gone this route and want to share what I've come to in the end and how convenient it is.

First, let's consider a method that is obvious and that I used for a long time

Links to project files

And to keep it simple, visual, and clear, I will describe everything using an abstract example of plugin development.

Let's open Visual Studio (I have the Community 2019 version. And yes – in Russian) and create a new solution. We'll name it MySuperPluginForRevit

We create a single plugin project compiled for different versions of Revit/AutoCAD

We're going to create a plugin for Revit for versions 2015-2020. So I will create a new project in the solution (Class Library .NET Framework) and name it MySuperPluginForRevit_2015

We create a single plugin project compiled for different versions of Revit/AutoCAD

We need to add references to the Revit API. Of course, we can add references to local files (you will need to install all necessary SDKs or all versions of Revit), but we’ll take the correct approach and connect a NuGet package. You can find a considerable number of packages, but I will use my own.

After connecting the package, right-click on the item «Links» and select the item «Migrate packages.config to PackageReference…»

We create a single plugin project compiled for different versions of Revit/AutoCAD

If panic starts here because the 'Copy locally’ option is not available in the package properties window, which we definitely need to set to false, don't panic – go to the project's folder, open the file with the .csproj extension in your preferred editor (I use Notepad++) and find the entry for our package. It currently looks like this:

1.0.0

We add the property runtime. It will look like this:

1.0.0
  runtime

Vanaf nu zullen de bestanden uit het pakket niet meer worden gekopieerd naar de uitvoermap tijdens het bouwen van het project.
Laten we verder gaan – stel dat onze plugin iets uit de Revit API zal gebruiken, dat in de loop van de tijd is veranderd met de uitgifte van nieuwe versies. Of we moeten gewoon iets in de code veranderen afhankelijk van de versie van Revit waarvoor we de plugin maken. Om dergelijke verschillen in de code op te lossen, zullen we voorwaardelijke compilatiesymbolen gebruiken. Laten we de projectinstellingen openen en naar het tabblad "Bouwen" gaan en in het veld "Voorwaardelijke compilatiesymbolen" schrijven R2015.

We create a single plugin project compiled for different versions of Revit/AutoCAD

Let op dat het symbool ook moet worden toegevoegd voor zowel de Debug- als de Release-configuratie.

En terwijl we in het eigenschappenvenster zijn, laten we direct naar het tabblad "Toepassing" gaan en in het veld "Standaardnaamruimte" het suffix verwijderen _2015, zodat onze naamruimte universeel en onafhankelijk van de assemblynaam is:

We create a single plugin project compiled for different versions of Revit/AutoCAD

In mijn geval worden de plugins van alle versies in één map geplaatst, daarom blijven de assembliesuffixen van soortgelijke _20xx. Maar u kunt het suffix ook uit de naam van de assembly verwijderen, als een andere indeling van de bestanden in verschillende mappen is voorzien.

Laten we naar de code van het bestand Class1.cs gaan en daar een bepaalde code simuleren met inachtneming van verschillende versies van 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", "Hallo Revit 2015");
#elif R2016
            TaskDialog.Show("ModPlus", "Hallo Revit 2016");
#elif R2017
            TaskDialog.Show("ModPlus", "Hallo Revit 2017");
#elif R2018
            TaskDialog.Show("ModPlus", "Hallo Revit 2018");
#elif R2019
            TaskDialog.Show("ModPlus", "Hallo Revit 2019");
#elif R2020
            TaskDialog.Show("ModPlus", "Hallo Revit 2020");
#endif
            return Result.Succeeded;
        }
    }
}

Ik heb meteen alle versies van Revit boven de versie 2015 overwogen (die beschikbaar waren op het moment van schrijven) en heb gelijk rekening gehouden met de voorwaardelijke compilatiesymbolen, die volgens hetzelfde sjabloon worden aangemaakt.

Laten we naar de belangrijkste attractie gaan. We creëren een nieuw project in onze oplossing, maar dan al voor de versie van de plugin voor Revit 2016. We herhalen alle bovengenoemde stappen, waarbij we uiteraard het getal 2015 vervangen door het getal 2016. Maar het bestand Class1.cs verwijderen we uit het nieuwe project.

We create a single plugin project compiled for different versions of Revit/AutoCAD

Het bestand met de benodigde code – Class1.cs – we already have it and we just need to insert a link to it in the new project. There are two ways to insert links:

  1. Long – right-click on the project, select the option “Add” -> “Existing item”, in the opened window, find the required file and instead of the option “Add”, select the option “Add as link»

We create a single plugin project compiled for different versions of Revit/AutoCAD

  1. Short – directly in the solution explorer, select the required file (or even files. You can even drag entire folders) and drag it to the new project while holding down the Alt key. When dragging, you will see that when you press the Alt key, the cursor on the mouse will change from a plus to an arrow.
    UPD: I introduced a bit of confusion in this paragraph — to move multiple files, you need to hold down Shift+Alt!

After carrying out the procedure, we will have a file in the second project Class1.cs with a corresponding icon (blue arrow):

We create a single plugin project compiled for different versions of Revit/AutoCAD

When editing code in the editor window, you can also choose the context of which project to display the code, allowing you to see and edit the code with different conditional compilation symbols:

We create a single plugin project compiled for different versions of Revit/AutoCAD

Using this scheme, we create all other projects (2017-2020). Life hack – if you drag files in the solution explorer not from the base project, but from a project where they are already inserted as links, you don't need to hold down the Alt key!

The described method works well until you add a new version of the plugin or add new files to the project – all of this becomes quite tedious. Recently, I suddenly realized how to resolve it with just one project, and we move on to the second method

The Magic of Configurations

After reading this far, you might exclaim, “Why did you describe the first method if the article is immediately about the second?!” I described it to clarify why we need conditional compilation symbols and in which places our projects differ. Now it becomes clearer which specific project differences we need to implement, leaving just one project.

And to make everything more obvious, we will not create a new project but make changes to our current project, created in the first way.

So, first of all, we remove all projects from the solution except for the main one (containing the actual files). That is, the projects for the versions 2016-2020. We open the folder with the solution and delete the folders of these projects.

In onze oplossing is er nog één project over — MySuperPluginForRevit_2015. We openen de eigenschappen ervan en:

  1. Op het tabblad «Toepassing» verwijderen we de suffix van de naam van de assemblage _2015 (je zult zo begrijpen waarom)
  2. Op het tabblad «Bouwen» verwijderen we het voorwaardelijke compilatiesymbool R2015 uit het overeenkomstige veld

Opmerking: in de laatste versie van Visual Studio is er een bug – voorwaardelijke compilatiesymbolen worden niet weergegeven in het eigenschappenvenster van het project, hoewel ze aanwezig zijn. Als je deze bug tegenkomt, moet je ze handmatig uit het .csproj-bestand verwijderen. Maar we moeten er toch in blijven werken, dus lees verder.

We hernoemen het project in het oplossingsoverzicht door de suffix te verwijderen _2015 en verwijderen vervolgens het project uit de oplossing. Dit is nodig voor het behoud van orde en de gevoelens van perfectionisten! We openen de map van onze oplossing, hernoemen daar op dezelfde manier de projectmap en laden het project weer in de oplossing.

We openen de configuratiebeheerder. We hebben de configuratie Release in principe niet nodig, dus verwijderen we deze. We maken nieuwe configuraties met de namen die we al gewend zijn R2015, R2016, …, R2020. Let op, dat het niet nodig is om parameters van andere configuraties te kopiëren en dat het niet nodig is om projectconfiguraties te maken:

We create a single plugin project compiled for different versions of Revit/AutoCAD

We gaan naar de map met het project en openen het bestand met de extensie .csproj in een editor naar keuze. Trouwens, het kan ook in Visual Studio worden geopend – je moet het project uitladen en vervolgens staat er in het contextmenu de benodigde optie:

We create a single plugin project compiled for different versions of Revit/AutoCAD

Bewerken in Visual Studio is zelfs te verkiezen, omdat de editor zowel uitlijnt als voorziet van hints.

In het bestand zien we de elementen PropertyGroup – bovenaan staat de algemene, en daarna volgen de condities. Deze elementen stellen de eigenschappen van het project in tijdens de bouw. Het eerste element, zonder condities, stelt de algemene eigenschappen in, terwijl de elementen met condities de eigenschappen veranderen afhankelijk van de configuraties.

We gaan naar het algemene (eerste) element PropertyGroup en kijken naar de eigenschap AssemblyName – dit is de naam van de assemblage en deze moet zonder suffix zijn _2015. Als er een suffix is, verwijderen we die.

We vinden het element met de voorwaarde

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

We hebben het niet nodig – we verwijderen het.

Het element met de voorwaarde

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

zal nodig zijn voor het werken in de ontwikkelings- en debugfase van de code. Je kunt de eigenschappen aanpassen aan je behoeften – verschillende uitvoerpaden instellen, voorwaardelijke compilatiesymbolen wijzigen, enz.

Laten we nu nieuwe elementen aanmaken PropertyGroup voor onze configuraties. Voor deze elementen hoeven we slechts vier eigenschappen in te stellen:

  • OutputPath – de uitvoermap. Ik stel een standaardwaarde in binR20xx
  • DefineConstants – symbolen voor conditionele compilatie. We dienen de waarde in te stellen TRACE;R20хх
  • TargetFrameworkVersion – de versie van het platform. Voor verschillende versies van de Revit API moeten verschillende platforms worden ingesteld.
  • AssemblyName – de naam van de assembly (d.w.z. de naam van het bestand). Je kunt rechtstreeks de gewenste naam van de assembly invoeren, maar voor de universele toepassing raad ik aan de waarde in te stellen op $(AssemblyName)_20хх. Hiervoor hebben we eerder de suffix uit de naam van de assembly verwijderd.

De belangrijkste eigenschap van al deze elementen is dat ze eenvoudig in andere projecten kunnen worden gekopieerd zonder veranderingen. Verder in het artikel zal ik de volledige inhoud van het .csproj-bestand bijvoegen.

Goed, we hebben de projecteigenschappen begrepen – dat is niet moeilijk. Maar wat doen we met de toegevoegde bibliotheken (NuGet-pakketten)? Als we verder kijken, zien we dat de toegevoegde bibliotheken worden ingesteld in de elementen ItemGroup. Maar helaas – dit element verwerkt de voorwaarden niet correct, zoals het element PropertyGroup. Dit kan zelfs een bug van Visual Studio zijn, maar als we meerdere elementen met configuratievoorwaarden instellen, en er verschillende verwijzingen naar NuGet-pakketten binnen invoegen, dan worden bij het wisselen van configuratie alle opgegeven pakketten aan het project toegevoegd. ItemGroup De element

Choose , komt ons te hulp, en werkt volgens de voor ons bekende logica.Door het element if-then-else.

te gebruiken, stellen we verschillende NuGet-pakketten in voor verschillende configuraties: , komt ons te hulp, en werkt volgens de voor ons bekende logica.Alle inhoud van csproj

Alle inhoud van 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

Let op, dat ik in een van de voorwaarden twee configuraties heb aangegeven via OF (Or). Op deze manier wordt het benodigde pakket aangesloten bij de configuratie Debug.

En nu zijn we bijna perfect. We laden het project opnieuw, schakelen de configuratie in die we nodig hebben, en selecteren in het contextmenu van de oplossing (niet het project) het item „Herstel alle NuGet-pakketten” en zien hoe onze pakketten veranderen.

We create a single plugin project compiled for different versions of Revit/AutoCAD

En op dit punt liep ik vast – om alle configuraties tegelijk te bouwen, zouden we gebruik kunnen maken van batchbuild (menu „Bouwen” -> “Batchbuild”), maar bij het wisselen van configuraties vindt er geen automatisch herstel van de pakketten plaats. En ook tijdens het bouwen van het project gebeurt dit niet, hoewel het, theoretisch gezien, zou moeten. Ik heb geen standaardoplossingen voor dit probleem gevonden. En het is waarschijnlijk ook een bug van Visual Studio.

Daarom besloten we om voor batchbuild een speciaal geautomatiseerd buildsysteem te gebruiken, Nuke. Eigenlijk wilde ik dit niet, omdat ik dit overbodig vind voor het ontwikkelen van plugins, maar op dit moment zie ik geen andere oplossing. En op de vraag „Waarom juist Nuke?” is het antwoord eenvoudig – we gebruiken het op het werk.

Dus, we gaan naar de map van onze oplossing (niet het project), houden de toets Shift ingedrukt en klikken met de rechtermuisknop op een lege plek in de map – in het contextmenu selecteren we het item „Open PowerShell-venster hier».

We create a single plugin project compiled for different versions of Revit/AutoCAD

Als je nukenog niet hebt geïnstalleerd, voer dan eerst het commando in:

dotnet tool install Nuke.GlobalTool --global

Nu voer je het commando in nuke en je krijgt de vraag om in te stellen nuke voor het huidige project. Ik weet niet hoe dit beter te formuleren in het Nederlands – in het Engels zou er staan: Could not find .nuke file. Do you want to setup a build? [y/n]

Druk op de toets Y en daarna volgen de specifieke instellingspunten. We hebben de eenvoudigste optie nodig met gebruik van MSBuild, daarom antwoorden we zoals op de screenshot:

We create a single plugin project compiled for different versions of Revit/AutoCAD

Laten we naar Visual Studio gaan, die ons voorstelt om de oplossing opnieuw te laden, omdat er een nieuw project is toegevoegd. We herladen de oplossing en zien dat we een project hebben build waarbij we alleen geïnteresseerd zijn in één bestand – Build.cs

We create a single plugin project compiled for different versions of Revit/AutoCAD

We openen dit bestand en schrijven een script om het project voor alle configuraties te bouwen. Of we gebruiken mijn script, dat je naar wens kunt aanpassen:

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;

    // Als de naam van de oplossing en de naam van het project (plugin) verschillend zijn, geef dan hier de naam van het project (plugin) op
    string PluginName => Solution.Name;

    Target Compile => _ => _
        .Executes(() =>
        {
            var project = Solution.GetProject(PluginName);
            if (project == null)
                throw new FileNotFoundException("Niet gevonden!");

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

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

                Logger.Normal($"Configuratie: {configuration}");

                build.Add(configuration);

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

We keer terug naar het PowerShell-venster en typen de opdracht opnieuw nuke (we kunnen de opdracht invoeren nuke met de benodigde Target. Maar we hebben er één Target, die standaard wordt uitgevoerd). Na het indrukken van de Enter-toets voelen we ons echte hackers, want zoals in de film zal het automatisch onze projectconfiguraties bouwen.

Overigens, je kunt PowerShell ook rechtstreeks vanuit Visual Studio gebruiken (menu “Weergave” -> “Andere vensters” -> “Pakketbeheer-console”), maar daar is alles zwart-wit, wat niet zo handig is.

Dit is het einde van mijn artikel. Ik ben ervan overtuigd dat je zelf wel uitkomt met de variant voor AutoCAD. Ik hoop dat het materiaal hier waardevolle ‘klanten’ zal vinden.

Bedankt voor uw aandacht!

Bron: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers 🔥 Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster