我們製作一個插件項目,針對不同版本的 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 進行編譯

如果此時你突然開始恐慌,因為在包屬性視窗中將沒有重要的項目“本地複製“,我們肯定需要將其設為值 ,然後不要驚慌 - 轉到專案所在的資料夾,在方便的編輯器(我使用 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хх。 但是,如果檔案應該位於不同的資料夾中,您也可以從組件名稱中刪除後綴。

我們來看文件代碼 Class1.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。但是文件 Class1.cs 從新項目中刪除。

我們製作一個插件項目,針對不同版本的 Revit/AutoCAD 進行編譯

包含所需程式碼的檔案 - Class1.cs – 我們已經有了它,我們只需要在新專案中插入它的連結。 插入連結有兩種方法:

  1. 長的 – 右鍵單擊該項目並選擇“»->«現有元素”,在打開的視窗中,找到所需的文件,而不是選項““選擇選項”新增為連接»

我們製作一個插件項目,針對不同版本的 Revit/AutoCAD 進行編譯

  1. – 直接在解決方案資源管理器中,選擇所需的文件(甚至文件,甚至整個資料夾),然後按住 Alt 鍵將其拖曳到新專案中。 拖曳時,您會看到當您按 Alt 鍵時,滑鼠遊標將從加號變為箭頭。
    UPD: 我在本段中犯了一點困惑 - 要傳輸多個文件,您應該按住 Shift + Alt!

執行該過程後,我們將在第二個專案中擁有一個文件 Class1.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 的選項。 我希望這裡提供的材料能夠找到它的“客戶”。

謝謝你的關注!

來源: www.habr.com

添加評論