我们针对不同版本的Revit / AutoCAD制作了一个插件项目,并进行了编译



在开发用于CAD应用程序的插件( 在我的示例中是 AutoCAD,Revit和Renga)时,随着时间的推移会出现一个问题-程序的新版本问世,它们的API更改以及新版本的插件都需要制作。


如果您只有一个插件,或者您仍然是自学成才的初学者,则只需复制项目,更改项目中的必要位置并构建新版本的插件即可。 因此,随后对准则的更改将导致人工成本的倍增。


随着经验和知识的积累,您会发现几种自动执行此过程的方法。 我就这样走了,我想告诉你我到底来了什么以及它有多方便。


首先,请考虑一种很明显且我已经使用很长时间的方法


链接到项目文件


为了使一切变得简单,清晰和易于理解,我将使用一个插件开发的抽象示例来描述一切。


打开Visual Studio(我具有Community 2019版本。是的-俄语)并创建一个新的解决方案。 我们称之为MySuperPluginForRevit


图片1

我们将为Revit制作2015-2020版本的插件。 因此,我们将在解决方案(Net Framework类库)中创建一个新项目,并将其命名为MySuperPluginForRevit_2015


image2

我们需要添加指向Revit API的链接。 当然,我们可以向本地文件添加链接(您将需要安装所有必需的SDK或Revit的所有版本),但是我们将在正确的路径上进行连接,并连接NuGet软件包。 您可以找到很少的软件包,但是我会用我自己的。


连接软件包后,右键单击“ 链接 ”项目,然后在菜单中选择“ 将packages.config传输到PackageReference ...


image3

如果您此时突然感到恐慌,因为软件包的属性窗口中没有重要的项目“ 本地复制 ”,我们肯定需要将其设置为false ,所以不要惊慌-转到项目文件夹并打开带有扩展名的文件。在方便的编辑器中使用csproj(我使用Notepad ++),我们在其中找到了有关软件包的记录。 现在看起来像这样:


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

向其中添加<ExcludeAssets>运行时</ ExcludeAssets>属性 。 原来是这样的:


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

现在,在构建项目时,软件包中的文件将不会复制到输出文件夹中。
我们走得更远-立即想象我们的插件将使用Revit API中的某些功能,该功能随着新版本的发布而发生了变化。 好吧,或者只是,我们需要更改代码,具体取决于我们为其制作插件的Revit版本。 为了解决代码中的此类差异,我们将使用条件编译符号。 打开项目属性,转到“ 程序集 ”选项卡,然后在“ 条件编译符号 ”字段中输入R2015


图4

请注意,必须为调试配置和发布配置都添加该符号。


好吧,当我们进入属性窗口时,我们立即转到“ 应用程序 ”选项卡,并在“ 默认名称空间 ”字段中删除后缀_2015 ,以便我们的名称空间是通用的并且独立于程序集名称:


图片5

就我而言,在最终产品中,所有版本的插件都放在一个文件夹中,因此我的程序集名称保留为_20xx形式的后缀。 但是,如果假定文件在不同文件夹中的位置,则也可以从程序集名称中删除后缀。


考虑到Revit的不同版本,我们传递到文件Class1.cs的代码并在那里模拟一些代码:


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

我立即考虑了Revit高于2015年版本的所有版本(在撰写本文时),并立即考虑了使用相同模板创建的条件编译符号。


我们转到主要亮点。 我们在解决方案中创建了一个新项目,仅适用于Revit 2016的插件版本。我们分别重复上述所有步骤,将数字2015替换为数字2016。但是我们从新项目中删除了Class1.cs文件。


图6

具有所需代码的文件-Class1.cs-我们已经有了,我们只需要在新项目中插入指向该文件的链接即可。 有两种插入链接的方法:


  1. 按-用鼠标右键单击项目,选择项目“ 添加 ”->“ 现有元素 ”,在打开的窗口中,找到所需的文件,而不是选项“ 添加 ”,选择选项“ 添加为链接

image7

  1. -在解决方案资源管理器中,选择所需的文件(甚至文件,甚至整个文件夹),然后按Alt键拖放到新项目中。 拖动时,您会看到按Alt键时,鼠标上的光标将从加号变为箭头。
    UPD:在本节中,我有些困惑-要传输多个文件,请按住Shift + Alt

完成该过程之后,我们将在第二个项目中看到带有相应图标(蓝色箭头)的Class1.cs文件:


图8

在编辑器窗口中编辑代码时,还可以在哪个项目的上下文中选择显示代码,这使您可以查看使用不同条件编译符号编辑的代码:


图9

在此计划下,我们创建了所有其他项目(2017-2020年)。 生活技巧-如果您不是从基础项目中而是从已经作为链接插入其中的项目中将文件拖放到解决方案资源管理器中,则您无法按住Alt键!


在添加新版本的插件或将新文件添加到项目中之前,所描述的选项非常好-所有这些变得非常乏味。 最近,我突然突然意识到如何通过一个项目解决所有这些问题,而我们正在转向第二种方法


配置魔术


在这里阅读之后,您可以大声疾呼:“如果这篇文章紧跟第二种方法,您到底在描述第一种方法是什么?!” 我描述了所有内容,以使我们更清楚为什么需要条件编译符号以及我们的项目在哪些地方有所不同。 现在,对于我们而言,我们需要明确实现哪些特定项目差异,而只剩下一个项目。


为了使所有事情变得更加明显,我们将不会创建新项目,而是会对以第一种方式创建的当前项目进行更改。


因此,首先,我们从解决方案中删除所有项目,除了主要项目(直接包含文件)之外。 即 版本2016-2020的项目。 打开解决方案文件夹,然后在其中删除这些项目的文件夹。


解决方案中还有一个解决方案-MySuperPluginForRevit_2015 。 我们打开它的属性,并:


  1. 在“ 应用程序”选项卡上,从程序集名称中删除后缀_2015 (这将清楚为什么)
  2. 在“ 程序集 ”选项卡上,从相应字段中删除R2015条件编译符号

注意:在最新版本的Visual Studio中,有一个小故障-条件编译符号不会显示在项目属性窗口中,尽管它们存在。 如果存在此故障,则需要从.csproj文件中手动将其删除。 但是,我们仍然在其中工作,请继续阅读。

通过删除后缀_2015在解决方案资源管理器窗口中重命名该项目,然后从解决方案中删除该项目。 这对于维持秩序和完美主义者的感觉是必要的! 我们打开解决方案的文件夹,以相同的方式重命名项目文件夹,然后将项目重新加载到解决方案中。


打开配置管理器。 原则上,我们不需要Release配置,因此我们将其删除。 我们正在创建我们已经熟悉的名称为R2015R2016 ,..., R2020的新配置。 请注意,您不需要从其他配置复制参数,也不需要创建项目配置:


image10

我们转到项目文件夹,并在方便的编辑器中打开扩展名为.csproj的文件。 顺便说一句,它也可以在Visual Studio中打开-您需要卸载项目,然后在上下文菜单中显示正确的项目:


image11

在Visual Studio中进行编辑甚至更可取,因为编辑器既可以对齐又可以提示。


在文件中,我们将看到PropertyGroup元素-最顶层是常规,然后是条件。 这些元素在项目的组装过程中设置项目的属性。 第一个没有条件的元素设置常规属性,而有条件的元素则分别根据配置更改某些属性。


我们转到PropertyGroup的常规元素(第一个元素),然后查看AssemblyName属性-这是程序集的名称,应该使用不带后缀_2015的程序集 。 如果有后缀,则将其删除。


我们找到一个有条件的元素


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

我们不需要它-我们将其删除。


条件条款


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

在代码开发和调试阶段的工作将是必要的。 您可以根据需要更改其属性-设置不同的输出路径,更改条件编译符号等。


现在为我们的配置创建新的PropertyGroup元素。 在这些元素中,我们只需要设置四个属性:


  • OutputPath是输出文件夹。 我设置标准值bin \ R20xx
  • DefineConstants-条件编译符号。 设置TRACE; R20xx
  • TargetFrameworkVersion-平台的版本。 对于不同版本的Revit API,必须定义不同的平台。
  • AssemblyName-程序集名称(即文件名)。 您可以直接编写所需的程序集名称,但是为了通用起见 ,建议您写入$(AssemblyName)_20xx的值。 为此,我们先前从程序集名称中删除了后缀

所有这些元素的最重要特征是可以将它们轻松复制到其他项目,而无需进行任何更改。 在本文的后面,我将附加.csproj文件的全部内容。


好吧,我们弄清楚了项目的属性-这并不困难。 但是如何处理插件库(NuGet程序包)。 如果进一步看,我们将看到插件库是由ItemGroup元素定义的。 但这是不幸的-这个元素不能正确处理条件,例如PropertyGroup元素。 也许这甚至是Visual Studio的故障,但是如果您使用配置条件设置几个ItemGroup元素,并在其中插入指向NuGet包的不同链接,那么当您更改配置时,所有指定的包都将连接到项目。


Choose元素对我们有帮助,它根据通常的if-then-else逻辑工作。


使用Choose元素,我们为不同的配置定义了不同的NuGet软件包:


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> 

请注意,在一种情况下,我通过OR(Or)指定了两种配置。 因此,在调试配置期间将连接必要的程序包。


在这里,我们几乎一切都是完美的。 我们重新加载项目,打开所需的配置,在解决方案(不是项目)的上下文菜单中调用“ 还原所有NuGet软件包 ”项,然后查看软件包的变化情况。


image12

在这个阶段,我陷入了停顿-为了立即收集所有配置,我们可以使用批处理程序集(菜单“ 汇编程序 ”->“ 批处理程序集 ”),但是在切换配置时,软件包不会自动恢复。 尽管在理论上应该如此,但在组装项目时也不会发生。 我没有找到通过标准方法解决此问题的方法。 而且很可能这也是Visual Studio的错误。


因此,对于批量组装,决定使用特殊的Nuke自动组装系统。 实际上,我并不想这样做,因为我认为在插件开发框架中没有必要,但是目前我没有其他解决方案。 还有“为什么要精确地给核弹?”这个问题很简单-我们在工作中使用它。


因此,转到解决方案的文件夹(而不是项目),按住Shift键并右键单击该文件夹中的空白处-在上下文菜单中,选择“ 此处打开PowerShell窗口 ”项。


image13

如果您尚未安装nuke ,请首先编写命令


 dotnet tool install Nuke.GlobalTool –global 

现在,编写nuke命令,将要求您为当前项目配置nuke 。 我不知道如何用俄语更正确地编写它-找不到.nuke文件将用英语编写。 您要设置一个版本吗? [y / n]


按Y键,将立即进行设置。 我们需要使用MSBuild的最简单的选项,因此我们按照屏幕截图所示进行回答:


图14

让我们继续到Visual Studio,因为已经向其中添加了新项目,所以它将为我们提供重新加载解决方案的方法。 我们重新启动该解决方案,然后看到我们有一个生成项目,其中我们仅对一个文件Build.cs感兴趣


图片15

打开此文件并编写脚本以针对所有配置构建项目。 好吧,或者使用我的脚本,您可以自己编辑:


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

我们返回到PowerShell窗口并再次编写nuke命令(您可以使用所需的Target编写nuke命令。但是我们有一个Target ,默认情况下会启动)。 按下Enter键后,我们会像真正的黑客一样,因为就像在电影中一样,我们的项目将自动组装为不同的配置。


顺便说一句,您可以直接从Visual Studio使用PowerShell(菜单“ 视图 ”->“ 其他窗口 ”->“ 程序包管理器控制台 ”),但是所有内容都是黑白的,这不是很方便。


我的文章到此结束。 我确定您可以自己弄清楚AutoCAD的选项。 我希望这里介绍的材料能够找到其“客户”。


感谢您的关注!

Source: https://habr.com/ru/post/zh-CN482308/


All Articles