How we automated releases of Speckle for Revit 2019, 2020 and 2021 with different versions of CefSharp
Written by Matteo Cominetti on
The Revit API hasn’t changed much across their 2019, 2020 and 2021 releases (for better or worse 😅), but our 2019 version of Speckle for Revit still wouldn’t play nicely with the newer ones. This is because both our connector and Revit use CefSharp, a .NET wrapper around the Chromium Embedded Framework (CEF), that allows us to create pretty UI using web frameworks.
While Revit 2019 is stuck with an old version of CefSharp 57.0.0, the other releases use version 65.0 and therefore we couldn't just reuse the same built files. Below an extract from the Building Coder website that elaborates on Revit’s use of CefSHarp:
Revit and Autodesk add-ins use the CEFsharp library internally for several features. Some third-party add-ins do so as well. Occasionally, when different versions of the library are used, it leads to instability issues for Revit. In order to avoid version conflicts, we are clarifying what CEFsharp version is being used: Revit uses CEFsharp version 57.0.0. In addition, Revit 2019.1 now forcibly loads a version of CEFsharp prior to add-in initialization. This means that add-ins which load a different version of the CEFsharp library may not function as expected. Autodesk recommends realigning add-ins to use the version provided by and loaded by Revit. The Revit 2019.1 features that use the CEFsharp library include the new Revit Home and the Site Collaboration with Civil 3D.
The solutions we though about were to either:
- load/use CefSharp inside Speckle in a way that would not conflict with Revit’s CefSharp
or
- build a new version of Speckle for Revit that targeted CefSharp 65.0.1.
Note: the latst release of CefSharp, at the moment of writing, is 79.1.360, not sure why Revit manages to always lag behind!
Loading CefSharp inside Speckle in a way that would not conflict with Revit’s CefSharp is possible, and there’s even a sample plugin that does so by using inter-process communication (IPC). But we felt this approach wasn’t ideal for us:
Whit this in mind, we felt quite comfortable transforming our SpeckleRevit project to instead output multiple releases, one for each Revit version. This is overall a better approach as it lets us reference each version of Revit individually and make sure we’re not calling deprecated methods and so on.
Multi-version .csproj project
Changing our SpeckleRevit.csproj file to build for multiple versions of Revit is a pretty straightforward task. I think I learned this trick back in the days of CASE Design, and it's just a matter of adding the required build configurations to the project and referencing the correct NuGet packages based on the selected configuration.
You can see an example of this on my (now historical) BCFier GitHub project, this approach is great as it also lets you have:
- different pre/post build actions, example
and as well
Compared with creating one projects for each version of Revit, you get to keep everything in one place minimising the risk of human mistakes when making changes. While it might not be ideal in every situation this approach works very well for us.
Migrate from packages.config to PackageReference
Together with adding the new build configurations, we decided to migrate the packages.config to PackageReference in our Visual Studio project. PackageReferenceis the new default way in which .NET projects format references. The new structure is much cleaner and simpler and also prevents a lot of the c*** that CefSharp usually injects in the .csproj file.
Here's the documentatio on how to migrate and here's our .csproj file before and after.
You can also notice the addition of some new PackageReference specific logic to reference different NuGets based on the current build configuration, the syntax is quite different for what was being used with packages.config.
<?xml version="1.0" encoding="UTF-8"?>
<!-- SWITCH REVIT NUGETS -->
<Choose>
<When Condition="'$(Configuration)' == 'Debug2019' Or '$(Configuration)' == 'Release2019'">
<ItemGroup>
<PackageReference Include="Autodesk.Revit.SDK">
<Version>2019.2.2</Version>
<ExcludeAssets>runtime</ExcludeAssets>
</PackageReference>
</ItemGroup>
</When>
<When Condition="'$(Configuration)' == 'Debug2020' Or '$(Configuration)' == 'Release2020'">
<ItemGroup>
<PackageReference Include="Autodesk.Revit.SDK">
<Version>2020.2.1</Version>
<ExcludeAssets>runtime</ExcludeAssets>
</PackageReference>
</ItemGroup>
</When>
<When Condition="'$(Configuration)' == 'Debug2021' Or '$(Configuration)' == 'Release2021'">
<ItemGroup>
<PackageReference Include="Autodesk.Revit.SDK">
<Version>2021.0.0</Version>
<ExcludeAssets>runtime</ExcludeAssets>
</PackageReference>
</ItemGroup>
</When>
</Choose>
One last hurdle
Now that we have added multi-configuration to our project and migrated it to use the PackageReference structure, we still had to instruct it to use specific versions of CefSharp based on the configuration selected. The CefSharp package is actually referenced by our UI project, not by SpeckleRevit directly, and SpeckleUiBase is referenced as a NuGet package. This added a few complications as we either had to:
- release multiple versions of the SpeckleUiBase NuGet package
or
- reference SpeckleUiBase directly
We opted for the latter option as it was simpler, and added the Ui project as a submodule, added multiple build configurations to it and also simplified it to use the PackageReference format.
Finally, the SpeckleUiBase.csproj file required this addition, to swap between CefSharp versions (same thing we did for the Autodesk NuGets):
<?xml version="1.0" encoding="UTF-8"?>
<!-- USE CEF 57 or 65 -->
<Choose>
<When Condition="'$(Configuration)' == 'Debug57' Or '$(Configuration)' == 'Release57' Or '$(Configuration)' == 'Release2019'">
<ItemGroup>
<PackageReference Include="CefSharp.Wpf">
<Version>57.0.0</Version>
</PackageReference>
</ItemGroup>
</When>
<When Condition="'$(Configuration)' == 'Debug65' Or '$(Configuration)' == 'Release65' Or '$(Configuration)' == 'Release2020' Or '$(Configuration)' == 'Release2021'">
<ItemGroup>
<PackageReference Include="CefSharp.Wpf">
<Version>65.0.1</Version>
</PackageReference>
</ItemGroup>
</When>
<Otherwise>
<ItemGroup>
<PackageReference Include="CefSharp.Wpf">
<Version>79.1.360</Version>
</PackageReference>
</ItemGroup>
</Otherwise>
</Choose>
Now in the configuration manager we can set Visual Studio to build SpeckleRevit with the Release2020 project configuration together with SpeckleUiBase with the Release65 project configuration (which uses CefSharp 65.0.1) by selecting the Release2020 solution configuration, and likewise for 2019 and 2020 configurations both debug and release mode.
Conclusion
It took a bit of effort but we did it, we automated build of multiple versions of SpeckleRevit targeting the correct CefSharp and Autodesk NuGets and now our magical CI/CD pipelines will automatically publish these versions for us. Whenever Revit 2022 will be released we'll be able to support it in a matter of minutes (if there are no breaking API changes of course). 🎉
Know of a better way? Have comments? Let us know on the forum!
Update
Also check out an alternative way to set up your solution using Shared Projects as suggested by Konrad: how to maintain Revit plugins for multiple versions continued
Feeback or comments? We'd love to hear from you in our community forum!