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

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

为 CAD 应用程序开发插件时(就我而言 这些是 AutoCAD、Revit 和 Renga)随着时间的推移,出现了一个问题 - 发布了新版本的程序,需要更改它们的 API 和新版本的插件。

当您只有一个插件或者您在这方面仍然是自学初学者时,您可以简单地复制该项目,更改其中必要的位置并组装新版本的插件。 因此,后续代码的更改将导致劳动力成本成倍增加。

随着您获得经验和知识,您将找到多种方法来自动化此过程。 我走了这条路,我想告诉你我最终得到了什么以及它是多么方便。

首先,让我们看一个显而易见且我已经使用了很长时间的方法。

项目文件的链接

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

让我们打开 Visual Studio(我有 Community 2019 版本。是的 - 俄语)并创建一个新的解决方案。 我们就这样称呼他吧 MySuperPluginForRevit

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

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

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

我们需要添加 Revit API 的链接。 当然,我们可以添加到本地文件的链接(我们需要安装所有必需的 SDK 或所有版本的 Revit),但我们将立即遵循正确的路径并连接 NuGet 包。 你可以找到很多包,但我会使用我自己的。

连接包后,右键单击该项目“引用“并选择项目”将packages.config移至PackageReference...»

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

如果此时你突然开始恐慌,因为在包属性窗口中将没有重要的项目“本地复制“,我们肯定需要将其设置为值 false,然后不要惊慌 - 转到项目所在的文件夹,在方便的编辑器(我使用 Notepad++)中打开扩展名为 .csproj 的文件,并在那里找到有关我们的包的条目。 她现在的样子是这样的:

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

为其添加一个属性 运行。 结果会是这样的:

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

现在,构建项目时,包中的文件将不会复制到输出文件夹。
让我们更进一步 - 让我们立即想象我们的插件将使用 Revit API 中的某些内容,当新版本发布时,它会随着时间的推移而发生变化。 好吧,或者我们只需要根据我们为其制作插件的 Revit 版本更改代码中的某些内容。 为了解决代码中的此类差异,我们将使用条件编译符号。 打开项目属性,转到“选项卡装配“和在领域”条件编译表示法“让我们写 R2015.

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

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

好吧,当我们在属性窗口中时,我们立即转到“选项卡应用“和在领域”默认命名空间» 去掉后缀 _2015这样我们的命名空间是通用的并且独立于程序集名称:

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

就我而言,在最终产品中,所有版本的插件都放入一个文件夹中,因此我的程序集名称仍保留形式的后缀 _20хх。 但是,如果文件应该位于不同的文件夹中,您也可以从程序集名称中删除后缀。

我们来看一下文件代码 类1.cs 并在那里模拟一些代码,考虑到不同版本的 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;
        }
    }
}

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

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

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

包含所需代码的文件 - 类1.cs – 我们已经有了它,我们只需要在新项目中插入它的链接即可。 插入链接有两种方法:

  1. 长的 – 右键单击​​该项目并选择“添加内容“->”现有元素”,在打开的窗口中,找到所需的文件,而不是选项“添加内容“选择选项”添加为连接»

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

  1. – 直接在解决方案资源管理器中,选择所需的文件(甚至文件,甚至整个文件夹),然后按住 Alt 键将其拖动到新项目中。 拖动时,您会看到当您按 Alt 键时,鼠标光标将从加号变为箭头。
    UPD: 我在本段中犯了一点困惑 - 要传输多个文件,您应该按住 Shift + Alt!

执行该过程后,我们将在第二个项目中拥有一个文件 类1.cs 带有相应的图标(蓝色箭头):

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

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

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

我们使用此方案创建所有其他项目(2017-2020)。 生活窍门 - 如果您在解决方案资源管理器中拖动文件不是从基础项目中拖动文件,而是从文件已作为链接插入的项目中拖动,则无需按住 Alt 键!

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

配置的魔力

读完这里,您可能会惊呼:“如果这篇文章直接介绍的是第二种方法,为什么还要描述第一种方法?!” 我描述了所有内容,以便更清楚地说明为什么我们需要条件编译符号以及我们的项目在哪些地方有所不同。 现在我们更清楚我们需要实施的项目到底有哪些差异,只剩下一个项目了。

为了让一切变得更明显,我们不会创建一个新项目,而是会对以第一种方式创建的当前项目进行更改。

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

我们还有一个项目需要做出决定 - MySuperPluginForRevit_2015。 打开其属性并:

  1. 在选项卡上“应用"从程序集名称中删除后缀 _2015 (稍后会清楚为什么)
  2. 在选项卡上“装配» 删除条件编译符号 R2015 从相应的字段

注意:最新版本的 Visual Studio 有一个错误 - 条件编译符号不显示在项目属性窗口中,尽管它们可用。 如果您遇到此故障,则需要从 .csproj 文件中手动删除它们。 然而,我们仍然需要努力,所以请继续阅读。

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

打开配置管理器。 美国配置 发布 原则上不需要,所以我们删除它。 我们使用我们已经熟悉的名称创建新的配置 R2015, R2016,... R2020。 请注意,您不需要从其他配置复制设置,也不需要创建项目配置:

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

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

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

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

在文件中我们将看到元素 物业集团 – 最上面是一般性的,然后是条件。 这些元素在构建项目时设置项目的属性。 第一个没有条件的元素设置一般属性,而有条件的元素会根据配置相应地更改一些属性。

转到公共(第一个)元素 物业集团 并查看房产 程序集名称 – 这是程序集的名称,我们应该不带后缀 _2015。 如果有后缀,则将其删除。

查找符合条件的元素

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

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

有条件的元素

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

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

现在让我们创建新元素 物业集团 对于我们的配置。 在这些元素中我们只需要设置四个属性:

  • 输出路径 - 导出目录。 我设置了默认值 宾R20xx
  • 定义常量 – 条件编译符号。 应指定该值 追踪;R20хх
  • 目标框架版本 – 平台版本。 不同版本的 Revit API 需要指定不同的平台。
  • 程序集名称 – 程序集名称(即文件名)。 您可以编写程序集的确切名称,但为了通用性,我建议编写值 $(程序集名称)_20хх。 为此,我们之前从程序集名称中删除了后缀

所有这些元素最重要的特点是它们可以简单地复制到其他项目中而无需进行任何更改。 在本文后面,我将附上 .csproj 文件的所有内容。

好的,我们已经弄清楚了该项目的属性——这并不难。 但是如何处理插件库(NuGet 包)。 如果我们进一步观察,我们会看到包含的库在元素中指定 物料组。 但运气不好 - 该元素错误地将条件处理为元素 物业集团。 也许这甚至是 Visual Studio 的一个小故障,但如果您指定多个元素 物料组 加上配置条件,并在里面插入不同的NuGet包链接,那么当你改变配置时,所有指定的包都会连接到项目中。

该元素来帮助我们 ,按照我们通常的逻辑工作 如果-然后-其他.

使用元素 ,我们为不同的配置设置不同的NuGet包:

所有内容 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>

请注意,在其中一种情况下,我通过指定了两种配置 或者。 这样在配置时就会连接所需的包 调试.

在这里,我们几乎拥有一切完美的东西。 我们重新加载项目,启用我们需要的配置,在解决方案(而不是项目)的上下文菜单中调用“项目”恢复所有 NuGet 包“我们会看到我们的包裹发生了怎样的变化。

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

在这个阶段我陷入了死胡同 - 为了一次收集所有配置,我们可以使用批量组装(菜单“装配“->”批量构建"),但是切换配置时,包不会自动恢复。 在组装项目时,这种情况也不会发生,尽管理论上它应该发生。 我还没有找到使用标准方法解决这个问题的方法。 这很可能也是 Visual Studio 的一个错误。

因此,对于批量装配,决定使用特殊的自动化装配系统 核弹。 我实际上并不想要这个,因为我认为这对于插件开发来说太过分了,但目前我没有看到任何其他解决方案。 对于“为什么要使用核武器?”这个问题答案很简单——我们在工作中使用它。

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

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

如果你没有安装它 核弹,然后先写命令

dotnet tool install Nuke.GlobalTool –global

现在写命令 核弹 系统会提示您配置 核弹 对于当前的项目。 我不知道如何用俄语更正确地写这个 - 用英语会写成 Could not find .nuke file。 您想设置构建吗? [是/否]

按Y键,就会出现直接设置项。 我们需要使用最简单的选项 构建,所以我们的回答如屏幕截图所示:

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

让我们转到 Visual Studio,它会提示我们重新加载解决方案,因为已添加了一个新项目。 我们重新加载解决方案,看到我们有一个项目 建立 我们只对一个文件感兴趣 - 构建.cs

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

打开此文件并编写一个脚本来构建所有配置的项目。 好吧,或者使用我的脚本,您可以编辑该脚本以满足您的需要:

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窗口,再次写入命令 核弹 (你可以写命令 核弹 表示需要的 目标。 但我们有一个 目标,默认运行)。 按下回车键后,我们会感觉自己像真正的黑客,因为就像电影中一样,我们的项目会自动组装成不同的配置。

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

我的文章到此结束。 我相信您可以自己找到 AutoCAD 的选项。 我希望这里提供的材料能够找到它的“客户”。

谢谢你!

来源: habr.com

添加评论