This is the final entry in my series about building Couchbase Lite .NET from source. As I mentioned in a previous post, I am too close and familiar with the library and I often forget that it is complicated and annoying to build without the amount of full time work that I have spent with it. This entry will feature something a bit different. Instead of going through a specific platform, it will go through what needs to happen to support a new platform that is not officially supported by Couchbase.
The first thing you need to do is determine which architectures your platform will run on. For each of these architectures, a native build of LiteCore is needed. This will most likely be the hardest part of figuring out how to support the new platform, but there are some things in place to give you some hints. There is a CMake project for LiteCore, for one thing. It’s definitely not pretty as I used it to learn about CMake, but any questions about it can be asked on the forums. If CMake understands your tooling, you can reuse the existing work on the new platform to build the shared C++ libraries.
After that, the new platform needs a C# support project. The responsibility of this project is to both provide a delivery mechanism for the native libraries that were built above and to provide some platform specific C# functionality (i.e. stuff that uses API that is not available in .NET Standard). Providing the native libraries is relatively simple. First, you need to include it in your Nuget package (example) by copying it into some arbitrary location.
Then, you need to tell the end user project to use the native library. The way this happens depends heavily on the platform but it almost always involves a targets file. This type of file will add functionality to you csproj project. For example, on iOS this means that the native library will be added as a bundle resource. UWP simply copies the relevant files to the output for the architecture being built. Android is an example of where a targets file is not used, and instead there is functionality to bake the native libraries directly into the managed DLL. So whatever your platform needs to do, this is where you take care of it. If you put the targets file into the build/<platform-moniker> folder of your Nuget package, it will be added to the user’s project when the Nuget package is added.
Sometimes dynamic registration of the native libraries is needed at runtime, and also the platform specific C# classes need to be registered. There is no enforced convention for this in Couchbase Lite, but the de facto convention I am using is to make a class in the namespace Couchbase.Lite.Support named after the target platform with a static Activate method. So, for example, UWP would be Couchbase.Lite.Support.UWP. Two examples of when dynamic library loading is needed is on .NET Core Windows (also .NET Framework, because the system architecture is not known until runtime) and iOS (because this is an AOT platform, and it won’t know to find libLiteCore in the application bundle).
As for the C# classes, I found that Microsoft provides a nice package for dependency injection that was developed as part of ASP .NET. It is now a standalone package that can be used for .NET Standard and so I am making use of it. The concept revolves around a collection of services that is transformed into a provider after startup initialization. There is no startup initialization in most Couchbase Lite applications, so instead the application can register services until the first service is used and after that things are frozen. However, as you can see, the way to add services is pretty simple and involves assigning a concrete class to an interface. The first three lines of the above immediately instantiate one, and the last one uses a Func<T> to lazily instantiate if needed (this is helpful because I don’t want to generate an implementation for this one if the user has already given one).
The interfaces that need implementations are in this folder, and one that is not is the Microsoft provided ILogProvider. IDefaultDirectoryResolver is a service that will get the default directory for a database where no directory is specified (this needs to be per platform since some of them are sandboxed and relying on the Environment class is not always a great idea for getting paths). IReachability is a service for reacting to changes in local network state. This is mostly the same for every platform except UWP, which throws NotSupportedExceptions during some key functions. ISslStreamFactory is a service for creating a TLS encrypted stream to an endpoint. I don’t want to have this service but it is needed because the current UWP is not compatible with the System.Net.Security Nuget package. ILogProvider is simply a service which can generate loggers to an arbitrary logging endpoint.
The goal of this post is to provide some tools to the open source community so that if a new platform is desired, things can be driven forward without simply filing an issue about “support for X platform.” Of course, I am willing to help out and provide insight if this process becomes difficult but the reality is that we cannot support all the platforms that people want with our current team size, so I hope this can give some power to the people!