.NET Core makes it convenient to develop and test C# code across platforms. On my current project, this means we can do much of our work on Macs without ever firing up a Windows VM.
Even the best abstraction layers occasionally leak, though. Here’s a story of an OSX-specific issue we encountered, what we learned, and how to resolve it.
So there I was, running an existing .NET Framework project on .NET Core for the first time (using .NET Core 1.1). The project uses a third-party library to communicate with a REST API. Like many problems in software development, it started with an error message:
System.PlatformNotSupportedException: The libcurl library in use (7.54.0) and its SSL backend ("SecureTransport") do not support custom handling of certificates. A libcurl built with OpenSSL is required.
For us, the exception came from within a third-party library, but you might encounter it if you were doing something advanced with HttpClient. For example, a contributor on the GitHub issue provided a short repro that attempts to check a certificate revocation list.
So how can we get libcurl built with OpenSSL? The easiest way is to install Homebrew’s
curl package, which provides both the
curl command line tool and the
libcurl library. It, too, uses SecureTransport by default, but we can choose OpenSSL instead:
brew install curl --with-openssl
After it’s installed, you’ll get a warning:
This formula is keg-only, which means it was not symlinked into /usr/local, because macOS already provides this software and installing another version in parallel can cause all kinds of trouble.
Let’s see what kind of trouble, shall we?
A Bad Solution
One suggestion on the issue thread is to disregard the warning and force-link libcurl (
brew link --force curl). This would promote
libcurl to paths within
/usr/local. Then feed the library path to the loader by setting an environment variable:
With this environment variable in place, when the loader goes looking for libraries, it will look in
/usr/local/lib first. This fixes our libcurl issue by prioritizing Brew’s version over the system version. Sweet!
But what if there were some other libraries in there that we didn’t want? Like, what if we needed Apple’s special version of libJPEG because it provides extra symbols that Homebrew’s libJPEG doesn’t offer?
Failed to load /usr/local/share/dotnet/shared/Microsoft.NETCore.App/1.1.2/libcoreclr.dylib, error: dlopen(/usr/local/share/dotnet/shared/Microsoft.NETCore.App/1.1.2/libcoreclr.dylib, 1): Symbol not found: __cg_jpeg_resync_to_restart Referenced from: /System/Library/Frameworks/ImageIO.framework/Versions/A/ImageIO Expected in: /usr/local/lib/libJPEG.dylib in /System/Library/Frameworks/ImageIO.framework/Versions/A/ImageIO Failed to bind to CoreCLR at '/usr/local/share/dotnet/shared/Microsoft.NETCore.App/1.1.2/libcoreclr.dylib'
Oops. This also happens with libtiff (
__cg_TIFFClientOpen) and libpng (
__cg_png_create_info_struct). For a practical repro of the conflict,
brew install imagemagick, which depends on all three. If you ever need to analyze problems like this more thoroughly, you can view library and symbol dependencies with the
To fix this, we could uninstall or unlink those libs, but that feels like setting another hack on a leaning pile of hacks.
A Better Solution
Instead of prioritizing all of Homebrew’s libraries, it’s safer to take just what we need:
DYLD_LIBRARY_PATH=/usr/local/opt/curl/lib. No force link necessary. Tada!
You could also use this technique to provide Homebrew’s OpenSSL to dotnet, instead of manually symlinking the libs as the current installation guide instructs.
The Best Solution
If you’re stuck on 1.x, the Better Solution above offers a feasible workaround that doesn’t require you to ignore your package manager’s warnings and introduce risk to other software on your system. If you’re able, though, now’s a good time to try out .NET Core 2.0. It should be releasing very soon!
Update: since this post was written, .NET Core 2.0 has been released!