mono/SkiaSharp

Distribute Linux Binaries for .NET Core on NuGet

mattleibow opened this issue ยท 45 comments

For most users of the common linux distros (Ubuntu, Debian, Red Hat, OpenSUSE, etc) it may be possible to distribute a binary in the NuGet package.

There are some limitations and reason why this is not such a good idea, but this is just an issue to track this thought.

Just want to link the main, but old, linux issue: #90


To make life easier for everyone, I moved code around and added a new target to the GN files. Now, the native libSkiaSharp build is done with GN and ninja directly: https://github.com/mono/SkiaSharp/wiki/Building-on-Linux

For people looking for prebuilt binaries: there is a prebuilt nuget package maintained by AvaloniaUI team. Despite the name it only contains libSkiaSharp.so. For docker images provided by microsoft you need to install libfontconfig1 package via apt-get.


Regarding providing prebuilt binaries in official package:

Please, link libstdc++ statically and use something with sufficiently old GLIBC as your build machine. This will allow to produce a single binary for almost every modern distro.

Ubuntu 14.04 is a good candidate for that since it has GLIBC 2.19 and modern compiler toolchain available from official repositories

@kekekeks, I am trying to use this package with .NET Core 2.0 in linux in a container based on https://hub.docker.com/r/microsoft/aspnetcore/ and get runtime exception

System.TypeInitializationException: The type initializer for 'SkiaSharp.SKImageInfo' threw an exception. ---> System.DllNotFoundException: Unable to load DLL 'libSkiaSharp': The specified module or one of its dependencies could not be found.

though I can see the file in the output folder runtimes\linux-x64\native\libSkiaSharp.so . I am new to linux and not sure where to look for a solution. Do you know what I might be doing wrong? Or do you have any manual on how to use it? I have only installed the package.

Try to use self-contained deployment, something like:

dotnet restore -r debian.8-x64
dotnet publish -r debian.8-x64 -o publish-dir

It should properly pack all dependencies for you.

You can also use microsoft/dotnet:2.0-runtime-deps-jessie as your base docker image in that case.

@kekekeks thank you for your response! I have tried different docker packages and different runtime configurations like debian.8-x64 and linux-x64. I can see libSkiaSharp.so in startup folder, but still get same exception. Probably I should wait for official support on Linux.

Check if you have all dependencies in your docker image. You can find them using lddtree libSkiaSharp.so. You image probably doesn't have libfontconfig1 package installed.

You can also try /lib/ld-linux.so.2 --verify ./libSkiaSharp.so or this simple utility to check for loader errors.

@kekekeks I run ldd libSkiaSharp.so because it looks like that my image does not have lddtree. And it indeed says that it cannot find it: libfontconfig.so.1 => not found.

I have done RUN apt-get update && apt-get install -y libfontconfig1 in my Dockerfile and and it worked ๐Ÿ˜„, thanks for the hints!

Do you need any particular feature of the recent SkiaSharp? It's kinda troublesome to build portable Linux binaries, so we avoid updating it unless it's really required.

No, it was just a question.
I've download libSkiaSharp.so from 1.59.2 release and it works,
Anyway I am working on mono, still need to migrate to .NET Core.

What about a native library for Raspberry Pi (Raspbian)? Any news on the Linux ARM front?

You need to build it manually. I think @mterwoord had some success with ARM

See https://github.com/terwoord/skiasharp-raspberry for my script.
I think, it currently only requires debian-based operating system. The script currently is hardcoded at (at least) one spot t o a specific base directory (/home/matthijs/skiasharp-raspberry).

PR's are welcome!

@mterwoord thanks for the information. So this is a cross-compilation, right? Would it be possible to compile skia/skiasharp on an actual Raspberry Pi? Did you try it?

This is cross-compilation, correct. I think I tried it, but the google tooling isn't available on arm.
Why would you want to compile on the actual pi?

@mterwoord nothing special, just out of curiosity. It is actually much easier and faster on an Ubuntu VM for me...

That's my whole point.. ;)
If you have improvements for my script, please make PR! Thanks! :)

@mterwoord it seems I need something on the 'rpi' directory. Could you add instructions on your readme file on how to configure your 'skiasharp-raspberry' base directory?

Edit build.sh, fix the BASE_DIR.
then run build.sh, possibly as admin (due to chroot usage)

Ok, this wont' work with an empty base directory. I think you need to describe what goes on your base directory. An empty one won't work, obviously:

+ export BASE_DIR=/home/parallels/Desktop/skia-rasp/skiasharp-raspberry
 + BASE_DIR=/home/parallels/Desktop/skia-rasp/skiasharp-raspberry
 + export BUILD_DIR=/home/parallels/Desktop/skia-rasp/skiasharp-raspberry/build
 + BUILD_DIR=/home/parallels/Desktop/skia-rasp/skiasharp-raspberry/build
 + export RPI_ROOT=/home/parallels/Desktop/skia-rasp/skiasharp-raspberry/rpi
 + RPI_ROOT=/home/parallels/Desktop/skia-rasp/skiasharp-raspberry/rpi
 + true
 + rm -Rf /home/parallels/Desktop/skia-rasp/skiasharp-raspberry/rpi
 + mkdir -p /home/parallels/Desktop/skia-rasp/skiasharp-raspberry/rpi
 + cd /home/parallels/Desktop/skia-rasp/skiasharp-raspberry/rpi
 + qemu-debootstrap --foreign --arch armhf jessie /home/parallels/Desktop/skia-rasp/skiasharp-raspberry/rpi http://ftp.debian.org/debian
 ./build.sh: line 17: qemu-debootstrap: command not found
 + chroot /home/parallels/Desktop/skia-rasp/skiasharp-raspberry/rpi apt -q -y --force-yes install build-essential
 chroot: failed to run command โ€˜aptโ€™: No such file or directory
 + chroot /home/parallels/Desktop/skia-rasp/skiasharp-raspberry/rpi apt -q -y --force-yes install gcc-multilib g++-multilib
 chroot: failed to run command โ€˜aptโ€™: No such file or directory
 + chroot /home/parallels/Desktop/skia-rasp/skiasharp-raspberry/rpi apt -q -y --force-yes install 
 libstdc++-4.8-dev
 chroot: failed to run command โ€˜aptโ€™: No such file or directory
 + chroot /home/parallels/Desktop/skia-rasp/skiasharp-raspberry/rpi apt -q -y --force-yes install 
libfontconfig1-dev
 chroot: failed to run command โ€˜aptโ€™: No such file or directory
 + true
 + rm -Rf /home/parallels/Desktop/skia-rasp/skiasharp-raspberry/build
 + mkdir -p /home/parallels/Desktop/skia-rasp/skiasharp-raspberry/build
 + cd /home/parallels/Desktop/skia-rasp/skiasharp-raspberry/build
 + git clone https://github.com/mono/SkiaSharp.git skia
 Cloning into 'skia'...

Start off with installing qemu-debootstrap.

I've already installed all the prerequisites as per your README file.

Well, my readme may be out of date. qemu-debootstrap isn't installed as it seems.

Ok, you need to add 'qemu-user-static' as a prerequisite, qemu-debootstrap is on it (ubuntu 16.04). I also had to install 'apt'. Couldn't you add the install commands of all prerequisites to the script itself?

Could you make a PR for that please? :)

When I run the chroot line
sudo chroot $RPI_ROOT apt -q -y --force-yes install build-essential
I get the following error:
chroot: failed to run command โ€˜aptโ€™: No such file or directory

note: apt is installed on my system

Don't know if this is relevant: the preceding qemu-debootstrap runs successfully, but at the beginning it shows the following message
W: Cannot check Release signature; keyring file not available /usr/share/keyrings/debian-archive-keyring.gpg

Interesting. I'm happy to help out, but am not too familiar with debootstrap, chrooting and stuff. I'm a Windows-based .NET developer. Was very proud of myself when I got this script working..

I have the same background as you, so I'm pretty unfamiliar with all this low level linux stuff too. ;)

Got it working with ubuntu 17.10 on a separate VM (WSL didn't work).
For those who are insterested in a pre built binary: https://github.com/toburger/skiasharp-raspberry/releases/tag/arm

@mterwoord @toburger I was also able to compile the library successfully. I modified the script a little bit and will make a PR soon. I found it strange that the compiled library is a 32-bit one, I thought that Raspbian was a 64-bit OS...

@mterwoord nothing special, I just thought that raspbian was 64-bit and was unconsciously expecting a 64-bit library.

@mterwoord I've submitted a PR, please take a look.

For Linux, I suggest that we package/distribute independently the unmanaged bits using the proper system packaging tool, and not try to solve the problem with NuGet.

Details here: #365

I've tested the arm native library with my rasp 2, it worked on Ubuntu MATE normally. On Raspbian Stretch, it seems that the g++4.8-multilib library is missing from the default apt-get repository. I didn't try to find one and went back to Ubuntu.

Curious, what behavior do you see on raspbian?

@mterwoord nevermind, I was putting an unnecessary dependency on g++4.8-multilib, which doesn't exist on Raspbian. The library worked fine on it too once I removed the dependency.

Just asking this question in multiple places:

Right now I have a x64 build for Linux that was compiled on Ubuntu 14.04. I am thinking of adding a few more to help the average dev use SkiaSharp without having to build their own. What other distros or CPUs would be helpful?

Leave a comment on this issue and track progress: #453

As long as you are linking libfreetype and libstdc++ statically, that x64 library should work across all modern glibc-based distros.

@kekekeks Thanks for your continual support. I hope to get the best out for everyone.

I created an issue to help track requests and implementations: #453

To make life easier for everyone, I moved code around and added a new target to the GN files. Now, the native libSkiaSharp build is done with GN and ninja directly: https://github.com/mono/SkiaSharp/wiki/Building-on-Linux

All right folks, I have been doing some work and I think I am able to meet you in the middle of "we don't really support Linux as it is not a priority" and "using Linux is a real pain because of X". Let me know what you think of this:

I have created a new package SkiaSharp.NativeAssets.Linux that will be going out with v1.68.0 that I would like to try. It will just contain the various Linux binaries that I have built and tested on. Right now, this is just going to be the binary I build on Ubuntu 16.04 amd64. I have done some testing and it appears to work on several distros (Ubuntu 14, OpenSUSe and CentOS), so I am going to put it in the linux-x64 runtime folder. (This may not work on alpine just yet - haven't tested - but I can always add a more specific build when I can.)

The overall result of this is that you will do everything as usual, but in the "app" part of the solution, just add the package. You should not release a NuGet to nuget.org that depends on this directly as you will then force all your uses to use my binary.

An example of a use case where a separate package is useful is this:

  • Matthew builds SkiaSharp and a binary for Ubuntu 16.
  • John builds an Ubuntu app and includes the SkiaSharp and SkiaSharp.NativeAssets.Linux packages. John is very happy.
  • Janet builds an Alpine app and includes SkiaSharp and her custom build of libSkiaSharp.so. Janet is not as happy as John. Janet has SKILLZ and can build for Alpine.
  • Joe is a great guy. Joe likes community. Joe likes Alpine Linux. Joe builds libSkiaSharp.so for Alpine. Joe creates a new NuGet package (JoesCodes.SkiaSharp.NativeAssets.Alpine) that just contains the Alpine binary.
  • Jane is also building for Alpine. Jane uses the JoesCodes.SkiaSharp.NativeAssets.Alpine package. Jane is happy.
  • Matthew has a look at JoesCodes.SkiaSharp.NativeAssets.Alpine and sees 1 million downloads! Matthew is blown away. Matthew knows everyone is using Alpine. Matthew has a chat to Big Boss: "We NEED Alpine". Big Boss has a look. Big Boss agrees. Matthew builds for Alpine and adds it to the SkiaSharp.NativeAssets.Linux package. Everyone is happy.
  • Janet and Jane now have options: use "official" or use "community". Janet is very happy. Jane is very happy.
    • Janet is Matthew's friend. Janet decides to use SkiaSharp.NativeAssets.Linux. Janet and Matthew are happy.
    • Jane is happy with JoesCodes.SkiaSharp.NativeAssets.Alpine. Jane is Joe's friend. Jane does not change.
  • Joe also has options: keep independent and free, or, update JoesCodes.SkiaSharp.NativeAssets.Alpine to be an empty shell package with a dependency.
    • Joe decides to update to depend on Matthew.
      • Janet updates. Janet is happy.
      • Jane updates. Jane gets the new package that gets Matthew's build. Jane is happy.
    • Joe stays free and builds the next version.
      • Janet updates. Janet is happy.
      • Jane updates. Jane gets Joe's new package. Jane is happy.

(Hope you folks like my story about Matthew and the Big Boss working to make life better for John, Janet, Joe and Jane. I had fun.)

Closing this as it is going out. Although it just has a single debian-based binary, more can be added if there is a demand. Since .NET Core is on Alpine, that will be the next build most likely.

To request a particular build of Linux to be included in the NuGet, just add a vote/comment to this issue and I will make sure to keep adding the most popular ones to the package: #453

If there are any issues with the new Linux bits, just open a new issue and let me know.