This may be already well known, and might be considered questionable OO logic, but I thought I’d share what I found to be a neat trick in case any others have hit a similar issue. Here is my setup:
- I have a primary .net application (“App.exe”) which uses Rhino3dm as a nuget package
- App.exe also makes REST calls to methods exposed by an .RHP plugin I wrote which is hosted in a separate RhinoCompute instance (“Plugin.rhp”)
- The primary App.exe compiles against an underlying C# class library project to do a bunch of the core CAD related operations (“Helper.dll”).
- However, the .RHP plugin also needs to be able to use logic from Helper.dll, and Helper.dll in order to be useful to both the App and the RHP, needs to be able to reference Rhino related classes as well (particularly the lower level math classes like points / vectors / transforms.)
So Helper.dll wants to know about Rhino.Geometry.Point3d, but will be consumed both by App.exe which wants Point3d to come from Rhino3dm, and Plugin.rhp which wants Point3d to come from RhinoCommon. This is somewhat related to the “Diamond Problem” in OO design.
Since the names of the classes and methods exposed by Rhino3dm are essentially a subset of that in RhinoCommon, the Helper.dll c# code (which only requires the subset in Rhino3dm) can actually compile against either of the nuget packages. By creating 2 .csproj files in the project directory, Helper.Rhino3dm.csproj and Helper.RhinoCommon.csproj, each can compile the exact same set of .cs files, but each just references the respective Nuget package. App.exe can then reference both Rhino3dm and Helper.Rhino3dm, and Plugin.rhp can reference RhinoCommon and Helper.RhinoCommon.
The tweaks required to make this work nicely are pretty simple. My build process compiles both of the Helper.* variants in parallel, and by default the compiler will place temporary build artifacts in /obj/ and the compilation output in /bin/. So we have to override the individual project settings to redirect these locations so the builds don’t muck each other up.
After some trial and error, here is what I found works. To tell the compiler to redirect the /obj/ contents per project, create a file called “Directory.Build.props” in your project root, and populate it with this:
<Project>
<PropertyGroup>
<BaseIntermediateOutputPath>obj\$(MSBuildProjectName)\</BaseIntermediateOutputPath>
</PropertyGroup>
</Project>
Once you have 2 .csproj files for your 2 builds, each referencing their respective rhino nuget package, inside of each file, the output location is redirected by adding this:
<PropertyGroup>
<OutputPath>bin\Debug\$(MSBuildProjectName)\</OutputPath>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
The last thing I ran into I’m not 100% clear on, but I ended up with multiple build tools generating redundant AssemblyInfo classes which broke the build. This I fixed by manually creating my own AssemblyInfo.cs file, and adding the other 2 elements to the .csproj above telling the compiler to not generate AssemblyInfo for me.
And voila! A single project directory / git repo which compiles 2 ways against 2 nuget packages. There are other ways to achieve this effect, probably with cleaner OO design, but given my use cases and existing infrastructure this ended up being a relatively clean path of least resistance to reuse my code without introducing interfaces or lots of runtime class conversions.
I have not run into this yet, but if your shared project did require code specific to RhinoCommon, you could define a compiler constant in the project which compiles against RhinoCommon called for instance “RHINO_COMMON”, and then wrap any code which requires RhinoCommon inside a preprocessor directive like this:
#if RHINO_COMMON
public void CallRhinoCommongThing(){...}
#endif
I’m curious if anyone else has run into this kind of issue, or any improvements on / alternatives to the above approach.