Skip to content

Proposal for better way to handle native binaries, with proof of concept NuGet packages #974

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
bording opened this issue Feb 23, 2015 · 36 comments

Comments

@bording
Copy link
Member

bording commented Feb 23, 2015

Looking through the issues list, one of the things I've seen come up a lot is the idea of improving how we're handling the native libgit libraries, in a way that works cross-platform, #837 and #493 for example. There has also been some talk moving the native libraries into a separate NuGet package, including some PRs that have attempted it but seem to have gotten stuck for various reasons( #896, #772).

After the change I made the current props file this weekend to keep it from copying the native binaries folder when not on windows, I started thinking about how I could leverage that into improve the above situation.

I spent some time working a proof of concept set of NuGet packages that seem to do exactly what I wanted them to:

bording/NativeBinaryNugetPackages

If you put them in a folder and set up a local package source, you can try them out. I've added them to projects in Windows, OSX Yosemite with Xamarin Studio, and Ubuntu 14.04, and everything works, with just the needed native libraries being automatically included as needed.

The way this works is that main LibGit2Sharp package just has the managed dll in it now, and nothing else. It has a dependency on what I've called LibGit2Sharp.NativeBinaries. In that package are all of the native binaries and a build\props file that conditionally adds the correct binaries based on what platform you are on.

Assuming everyone likes this approach, I'm proposing making the changes needed to go forward with this new packaging scheme.

One of the benefits I see of this is that it can simplify the main codebase, because we can just have a package dependency on the NativeBinaries package like any other managed project would. Anyone who grabs a copy of the code would just have it work in any environment!

I can also see moving all of the stuff that is currently in the repo for managing the native builds out into a separate LibGit2Sharp.NativeBinaries repo, which is where the libgit2 submodule could live along with the nuspec for building the native package.

Thoughts?

@bording
Copy link
Member Author

bording commented Feb 23, 2015

Oh yeah, one other thing it could do is simplify the CI builds since the NuGet package restore would bring down the needed native library instead of needing to build it directly.

@nulltoken
Copy link
Member

/cc @ethomson

@ethomson
Copy link
Member

So this seems like a reasonable approach to detaching the libgit2 libraries from the built LibGit2Sharp libraries themselves.

What I don't see (and maybe I'm just not looking hard enough). Am I also able to make the LibGit2Sharp project depend on the LibGit2Sharp.NativeBinaries nuget package? So that we can get the checked in libgit2 DLLs out of the LibGit2Sharp repository? That's the big win, in my opinion.

@nulltoken
Copy link
Member

Am I also able to make the LibGit2Sharp project depend on the LibGit2Sharp.NativeBinaries nuget package?

I think that's the general idea.

One thing that we would have to properly define though is the versioning strategy for the LibGit2Sharp.NativeBinaries. Although libgit2 has a versioning strategy, we tend to pick the latest master that doesn't cause any regression in the managed tests.

@ethomson @bording Any semver compliant idea?

@ethomson
Copy link
Member

Also, one minor nit, libgit2.license.txt remains included in the LibGit2Sharp nupkg. I think it should be in the LibGit2Sharp.NativeBinaries.nupkg instead.

@bording
Copy link
Member Author

bording commented Feb 23, 2015

Am I also able to make the LibGit2Sharp project depend on the LibGit2Sharp.NativeBinaries nuget package?

Yes, the LibGit2Sharp project would also be able to depend on the LibGit2Sharp.NativeBinaries package, removing the libgit2 dlls from the repo.

Also, one minor nit, libgit2.license.txt remains included in the LibGit2Sharp nupkg.

Yeah, I was just putting these packages together manually with NuGet Package Explorer, and didn't move that one over. The actual implementation that was generating the packages via a nuspec file would have that sorted correctly.

@bording
Copy link
Member Author

bording commented Feb 23, 2015

Any semver compliant idea?

The way I've set the package up right now, I'm not sure it would be super-useful on it's own, so I'm not sure it needs a separate versioning strategy. I was thinking of it as more of part of our release cycle, where we just happen to split things up into multiple packages.

Would we ever want to update just the native binaries and not push out a new version of of the managed dll to use it?

If not, then I think the NativeBinaries package would just have the same version as the main LibGit2Sharp package, and it would have a dependency on the exact version of the NativeBinaries package.

We could always include the libgit2 commit sha in the description of the package it that would be useful.

The other option I could see for the NativeBinaries package would make it just track the actual libgit2 releases, so that would only be updated when they release a new version. We could then make a prerelease package if we needed something that hadn't been released yet.

However, we'd end up needing to tie our releases to theirs then, because I don't believe a non-prerelease package can depend on a prerelease package. I need to double check that to confirm, though.

@nulltoken
Copy link
Member

@bording We only recently started to publish pre release packages. In the past, it happened that we upgraded libgit2 without publishing LibGit2Sharp.

We now leverage AppVeyor to build the packages for us on merge. We don't publish them automatically though.

I wonder if we couldn't do the same thing with the libgit2 build.

One thing we may plan for is that a libgit2 version may actually cause some previously passing LibGit2Sharp tests to fail.

So, in order to check that a libgit2 NuGet package works, we would download it, reference it locally, run the tests. Once everything is ok, we would publish the libgit2 package. then we would open a PR on LibGit2Sharp referencing it.

@rubberduck203
Copy link
Contributor

Out of curiosity, would this mean I would no longer have to explicitly copy and include the ---libgit2--- native binaries in my installer package? Maybe I'm misunderstanding something.

@bording
Copy link
Member Author

bording commented Feb 23, 2015

I wonder if we couldn't do the same thing with the libgit2 build.

I would think that we'd be able to do some sort of automation there, but it does become a bit more complicated because we need the binaries for windows, linux and mac to be included in the package.

They'd probably have to be built and commited separately, and then trigger the automated package build after all the new binaries are in the repo.

@bording
Copy link
Member Author

bording commented Feb 23, 2015

Out of curiosity, would this mean I would no longer have to explicitly copy and include the ---libgit2--- native binaries in my installer package? Maybe I'm misunderstanding something.

LibGit2Sharp would still need the native libgit2 binaries to work, so I guess it depends on what sort of installer package you're talking about.

If you're talking about some sort of setup program or msi then you'll still need to include those the native binaries as part of that.

@nulltoken
Copy link
Member

I would think that we'd be able to do some sort of automation there, but it does become a bit more complicated because we need the binaries for windows, linux and mac to be included in the package.

@bording Oh! I didn't realize that you were after building a multi-platform package. Yes indeed, that may add some complexity ;-)

Full disclosure: I'm not a *nix person. However, I was under the impression that building a "one-package-fits-all-size" for Linux would be complex (see GitTools/GitVersion#264 (comment)) from @carlosmn.

@bording
Copy link
Member Author

bording commented Feb 23, 2015

Oh! I didn't realize that you were after building a multi-platform package. Yes indeed, that may add some complexity ;-)

Yes, the sample NativeBinaries package I made includes binaries for windows, linux, and mac. I was trying to make getting the needed native binaries as dead simple as possible!

The package installs the binaries next to the LibGit2Sharp.dll. I tested which paths mono would look for the libraries, and, as of Mono 3.12 at least, it first looks at the directory where the requesting assembly is located, and then goes on to look at the platform-specific paths.

The mono docs also mention that it will do this:

http://www.mono-project.com/docs/advanced/pinvoke/dllnotfoundexception/

I didn't dig back into previous versions to see if this was something new or not.

@nulltoken
Copy link
Member

/cc @Therzok

@rubberduck203
Copy link
Contributor

Thanks for clarifying @bording. Sorry to interupt. 👍

@Therzok
Copy link
Member

Therzok commented Feb 23, 2015

@bording You can do some runtime linker magic to make that actually work. In the current situation, looking in the current directory isn't enough.

What the problem is: when you run otool -L on a library on Mac for example, it'll show you the absolute resolution paths for the native library. I think ld --list is used on Linux. So the paths need to be changed from absolute to relative to work on all systems (they work on your system due to them existing at the specified absolute paths).

The problem gets worse when you have relative dependencies (i.e. libssh2).

How I solved this: On Mac, you can use install_name_tool for this job (see here). On Linux, you'd need to find the equivalent (there is one, but I don't recall the name).

Optionally, this could be done at libgit2's cmake level by using this guide.

@Therzok
Copy link
Member

Therzok commented Feb 23, 2015

On a side note: The above solutions do not interfere with anything regarding the user's system. All binaries are shipped as-is with no extra dependency on having X installed.

Preference: Once normal binaries get solved, we could also go for a route where the native binary is also shipped with libssh2 compilation enabled. I could help get that build system up, as I've done it manually quite a few times. 👍

@bording
Copy link
Member Author

bording commented Feb 23, 2015

@Therzok So are you saying that the library itself has some hardcoded path dependencies that require some special treatment?

If not, them I'm a bit confused, because I tested the libraries I included in my sample package, and they definitely worked, with mono finding the library from the DllImport.

@Therzok
Copy link
Member

Therzok commented Feb 23, 2015

@bording I'm saying that the runtime linker itself (not mono) will use absolute paths in resolving it. It's going to resolve the path by identity - first line in the otool/ld output - or at least, that was my last experience with using these libs.

How did you use/test the binaries?

If you're using something in the lines of build.libgit2sharp.sh, that automatically solves the pain by adding them to the (DY)LD_LIBRARY_PATH. If you just created an app, copied the native binaries and libgit2sharp dlls in the app output directory and it worked, I'm amazed - it shouldn't.

@bording
Copy link
Member Author

bording commented Feb 23, 2015

If you just created an app, copied the native binaries and libgit2sharp dlls in the app output directory and it worked, I'm amazed - it shouldn't.

That's exactly what I did! I saw how build.libgit2sharp.sh was setting the (DY)LD_LIBRARY_PATH and read up on them.

Then I used the MONO_LOG_LEVEL=debug setting mentioned here:

http://www.mono-project.com/docs/advanced/pinvoke/dllnotfoundexception/

and I could see all the places that mono was looking for the library, and the first place it is looking is the directory that the referencing library is located.

As that page from the docs mentions:

If a library location has not been explicitly specified in a DllMap entry in an application or assembly .config file, Mono will search for a library in a few places:

  • The directory where the referencing image was loaded from.
  • In any place the system’s dynamic loader is configured to look for shared libraries. For example on Linux this is specified in the $LD_LIBRARY_PATH environment variable and the /etc/ld.so.conf file. On windows the $PATH environment variable is used, instead.

Everything was definitely working just like that when I was testing it out. Could this be something new in a recent mono release? Perhaps I need to go digging through the release notes...

@Therzok
Copy link
Member

Therzok commented Feb 24, 2015

Then I have no issues with this and it fulfills its goal. 👍

@bording
Copy link
Member Author

bording commented Feb 24, 2015

@Therzok Cool, you had me worried I had missed something major in my testing this weekend! 😄

@Therzok
Copy link
Member

Therzok commented Feb 24, 2015

I think the issue I was hitting was specifically related to runtime dependencies (such as libssh2). But that's out of scope.

@bording
Copy link
Member Author

bording commented Feb 24, 2015

Since it seems like people are cool with this, I've begun making changes here

I put the example versions of those nuget packages up on myget at

https://www.myget.org/F/bording-libgit2/

If you want to check out the branch so far, add that as a package source, and you'll be able to restore successfully.

I'll open a PR for this with a list of what else I'm thinking needs to be done and some followup questions tomorrow!

@nulltoken
Copy link
Member

I was under the impression that building a "one-package-fits-all-size" for Linux would be complex (see GitTools/GitVersion#264 (comment)) from @carlosmn.

@bording @Therzok Bump?

@bording
Copy link
Member Author

bording commented Feb 24, 2015

As @Therzok and I discussed above, mono doesn't have a problem loading the libgit2 lib when it's located next to LibGit2Sharp.dll.

So it looks like the potential for concern would be this:

The way to distribute binary libraries on unix is to make use of the package manager for a particular OS of family which can depend on the zlib, openssl, libssh2 and http-parser packages. Otherwise you're either going to have to ship a very limited version of libgit2 or field that kind of support requests.

It sounds like the complication comes in with the other libgit dependencies. I wonder what the "very limited version" of libgit2 would look like?

It still seems better to me to include some sort of native binary in the nuget package that at least gets people started and able to run something. We could always include a readme.txt in the package that would include instructions for building their own if they needed the extra features. Or perhaps would could include a version with the external dependencies and list those as requirements?

I guessing we don't want to go down the rabbit hole of actually adding/maintaining packages to the OS package managers?

@nulltoken
Copy link
Member

It sounds like the complication comes in with the other libgit dependencies

@bording Shouldn't we also consider the processor architecture on Linux?

@Therzok
Copy link
Member

Therzok commented Feb 24, 2015

Mono is 32bit only. It only supports 32bit invokes iirc.

@nulltoken
Copy link
Member

I've recently read something about x64 being worked on.

@bording
Copy link
Member Author

bording commented Feb 24, 2015

There are 64bit versions of mono, but on OSX at least, you currently have to compile from source to get it.

http://www.mono-project.com/docs/about-mono/supported-platforms/osx/

As far as other platforms, here is a list:

http://www.mono-project.com/docs/about-mono/supported-platforms/

There are several more exotic architectures that they list as supported, but I'm not even sure where'd we get access to those to build a library!

@nulltoken
Copy link
Member

Taking out of the loop the dependencies, if we compile libgit2 on Ubuntu, could the produced binary be interoped as is from Mono on others distros?

Sorry for the dumb question. cough Asking for a friend cough

@Therzok
Copy link
Member

Therzok commented Feb 24, 2015

No idea. This is out of mono scope, but binary compatibility between distros.

@nulltoken
Copy link
Member

but binary compatibility between distros

@Therzok Sorry. I didn't understand the last part of your sentence. 😖

@bording
Copy link
Member Author

bording commented Feb 24, 2015

So, I just spent some time chatting with a friend of mine who knows more about all of this than I do, and it sounds like for the most part the library should just work, assuming the dependent libraries are installed on the machine.

He did mention a potential for a binary compatibility problem based on the version of glibc used to compile vs. the installed version on the machine.

Based on that I think we could:

  • Include a version of the unix libraries
  • List the needed dependencies, which they could install if missing
  • Include instructions for building a copy of libgit2 if the provided one isn't good enough

That seems like a reasonable approach to me, and I think it makes for a better development experience when using LibGit2Sharp on a non-windows platform.

@bording
Copy link
Member Author

bording commented Feb 25, 2015

Assuming we want to try and get the unix binaries included, I have a way to make that work nicely inside the NuGet package, as my example NativeBinaries packages demonstrates.

Even if we decide it's too much of a hassle or too problematic to include the unix binaries, I think there is still enough benefit to move forward with this for just the windows binaries at least!

@nulltoken
Copy link
Member

Superseded by #984

bording added a commit that referenced this issue May 28, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants