Skip to content
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

Implement distribution of dynamic builds #2675

Merged
merged 34 commits into from
Feb 11, 2022

Conversation

hasufell
Copy link
Member

@hasufell hasufell commented Feb 1, 2022

A generated tarball can be tested here: https://downloads.haskell.org/ghcup/tmp/hls-dyn/

Building tarball

This needs patchelf and probably GNU make installed.

HLS_VERSION=1.6.1.0 make hls
HLS_VERSION=1.6.1.0 make bindist

This reads the file bindist/ghcs to determine what ghcs to build for.

Assuming the -dynamic patch is already applied to the cabal file.

Install

Install via e.g. make PREFIX=$HOME/.ghcup/hls/ install or so. then run ~/.ghcup/hls/bin/haskell-language-server-8.10.7 --help.

How

  1. we build in an isolated cabal store-dir
  2. all *.so libraries are copied out of that directory into a lib/ghc-<ver> subdir
  3. binaries are copied into bin subdir
  4. we use patchelf to set rpath to $ORIGIN/../lib/ghc-<ver>, similar to what GHC bindists to
  5. now we still lack the dyn libraries from GHC itself... we detect them by parsing ghc --print-libdir and adding all subdirs of it to LD_LIBRARY_PATH in a wrapper script
  6. everything is tar'ed up with an installation makefile

So this should work with an arbitrary system-installed GHC that is in PATH. The ghc version isn't checked explicitly, but could.


@hasufell hasufell force-pushed the hasufell/PR/distribute-dyn-builds branch from 10ca101 to 245951f Compare February 1, 2022 16:07
@fendor fendor requested a review from wz1000 February 1, 2022 16:07
@hasufell hasufell marked this pull request as draft February 1, 2022 16:15
@hasufell hasufell force-pushed the hasufell/PR/distribute-dyn-builds branch 5 times, most recently from b5ebb1e to 056f22d Compare February 1, 2022 19:05
@pepeiborra
Copy link
Collaborator

Can you teach ghcup to patchelf the rpaths after installation to point to the ghcup-installed GHC?

@hasufell hasufell force-pushed the hasufell/PR/distribute-dyn-builds branch from 056f22d to 504494c Compare February 1, 2022 19:22
@hasufell
Copy link
Member Author

hasufell commented Feb 1, 2022

Can you teach ghcup to patchelf the rpaths after installation to point to the ghcup-installed GHC?

Hmm... that's less flexible though. E.g. people can relocate their ghcup installation to a different directory. The current wrapper also doesn't really rely on ghcup.

Additionally, many systems won't have the patchelf binary.

@hasufell
Copy link
Member Author

hasufell commented Feb 1, 2022

But I agree that there's a chance that lots of configurations won't have a valid/matching ghc binary in PATH... what ghcup could probably do is to set GHC_LIBDIR variable in the wrapper. Then the wrapper won't have to detect it.

E.g.

GHC_LIBDIR="$(ghcup whereis -d ghc ${GHC_VERSION})/../lib/ghc-${GHC_VERSION}/"

@pepeiborra
Copy link
Collaborator

pepeiborra commented Feb 1, 2022

This relies on the HLS wrapper to find and call the right HLS binary for the GHC version in scope. So I think we want the HLS wrapper to be fully statically linked, right?

@hasufell
Copy link
Member Author

hasufell commented Feb 1, 2022

This relies on the HLS wrapper to find and call the right HLS binary for the GHC version in scope.
So I think we want the HLS wrapper to be fully statically linked, right?

Well... haskell-language-server-wrapper uses the same wrapping mechanism via the script currently. However I guess that isn't strictly necessary, since it doesn't need to be dynamically linked. But I'm not sure that solves anything?

@pepeiborra
Copy link
Collaborator

This relies on the HLS wrapper to find and call the right HLS binary for the GHC version in scope.
So I think we want the HLS wrapper to be fully statically linked, right?

Well... haskell-language-server-wrapper uses the same wrapping mechanism via the script currently. However I guess that isn't strictly necessary, since it doesn't need to be dynamically linked. But I'm not sure that solves anything?

I suppose it doesn't - if the dyn wrapper doesn't work, the dyn binary won't work either

@pepeiborra
Copy link
Collaborator

Trying the binaries in CentOS 8:

─>$ PATH=~/.ghcup/bin:$PATH /usr/local/bin/haskell-language-server-8.10.7                                                                                                                                                                                    
/usr/local/lib/haskell-language-server-1.6.1.0/bin//haskell-language-server-8.10.7: /lib64/libm.so.6: version `GLIBC_2.29' not found (required by /usr/local/lib/haskell-language-server-1.6.1.0/bin/../lib/8.10.7/libHSghc-lib-parser-8.10.7.20210828-d7d9fb1
3357a39514f106bdbdc494867bddaddc4200b6c3bc22a2aa4f48fdf56-ghc8.10.7.so)                                                                                                                                                                                       
/usr/local/lib/haskell-language-server-1.6.1.0/bin//haskell-language-server-8.10.7: /lib64/libm.so.6: version `GLIBC_2.29' not found (required by /usr/local/lib/haskell-language-server-1.6.1.0/bin/../lib/8.10.7/libHSQuickCheck-2.14.2-5a45c5c8a03bd14b589d
f42c0b12fe716b3711e5e6d5b5de16efb8024cc8b008-ghc8.10.7.so)                                                                                                                                                                                                    
/usr/local/lib/haskell-language-server-1.6.1.0/bin//haskell-language-server-8.10.7: /lib64/libc.so.6: version `GLIBC_2.33' not found (required by /usr/local/lib/haskell-language-server-1.6.1.0/bin/../lib/8.10.7/libHSdirect-sqlite-2.3.26-818fd3d15f09228e8
0772b43db7c405bf637b666eb2b51d62d1a7122c473f6bb-ghc8.10.7.so)                                                                                                                                                                                                 
/usr/local/lib/haskell-language-server-1.6.1.0/bin//haskell-language-server-8.10.7: /lib64/libm.so.6: version `GLIBC_2.29' not found (required by /usr/local/lib/haskell-language-server-1.6.1.0/bin/../lib/8.10.7/libHSdirect-sqlite-2.3.26-818fd3d15f09228e8
0772b43db7c405bf637b666eb2b51d62d1a7122c473f6bb-ghc8.10.7.so)                                                                                                                                                                                                 
/usr/local/lib/haskell-language-server-1.6.1.0/bin//haskell-language-server-8.10.7: /lib64/libm.so.6: version `GLIBC_2.29' not found (required by /usr/local/lib/haskell-language-server-1.6.1.0/bin/../lib/8.10.7/libHSscientific-0.3.7.0-5427bae3aaeccd2f186
ae821456cae552a9bb2fa1eabe44a964ec507c514aec7-ghc8.10.7.so)                                                                                                                                                                                                   

@hasufell
Copy link
Member Author

hasufell commented Feb 1, 2022

Trying the binaries in CentOS 8:

─>$ PATH=~/.ghcup/bin:$PATH /usr/local/bin/haskell-language-server-8.10.7                                                                                                                                                                                    
/usr/local/lib/haskell-language-server-1.6.1.0/bin//haskell-language-server-8.10.7: /lib64/libm.so.6: version `GLIBC_2.29' not found (required by /usr/local/lib/haskell-language-server-1.6.1.0/bin/../lib/8.10.7/libHSghc-lib-parser-8.10.7.20210828-d7d9fb1
3357a39514f106bdbdc494867bddaddc4200b6c3bc22a2aa4f48fdf56-ghc8.10.7.so)                                                                                                                                                                                       
/usr/local/lib/haskell-language-server-1.6.1.0/bin//haskell-language-server-8.10.7: /lib64/libm.so.6: version `GLIBC_2.29' not found (required by /usr/local/lib/haskell-language-server-1.6.1.0/bin/../lib/8.10.7/libHSQuickCheck-2.14.2-5a45c5c8a03bd14b589d
f42c0b12fe716b3711e5e6d5b5de16efb8024cc8b008-ghc8.10.7.so)                                                                                                                                                                                                    
/usr/local/lib/haskell-language-server-1.6.1.0/bin//haskell-language-server-8.10.7: /lib64/libc.so.6: version `GLIBC_2.33' not found (required by /usr/local/lib/haskell-language-server-1.6.1.0/bin/../lib/8.10.7/libHSdirect-sqlite-2.3.26-818fd3d15f09228e8
0772b43db7c405bf637b666eb2b51d62d1a7122c473f6bb-ghc8.10.7.so)                                                                                                                                                                                                 
/usr/local/lib/haskell-language-server-1.6.1.0/bin//haskell-language-server-8.10.7: /lib64/libm.so.6: version `GLIBC_2.29' not found (required by /usr/local/lib/haskell-language-server-1.6.1.0/bin/../lib/8.10.7/libHSdirect-sqlite-2.3.26-818fd3d15f09228e8
0772b43db7c405bf637b666eb2b51d62d1a7122c473f6bb-ghc8.10.7.so)                                                                                                                                                                                                 
/usr/local/lib/haskell-language-server-1.6.1.0/bin//haskell-language-server-8.10.7: /lib64/libm.so.6: version `GLIBC_2.29' not found (required by /usr/local/lib/haskell-language-server-1.6.1.0/bin/../lib/8.10.7/libHSscientific-0.3.7.0-5427bae3aaeccd2f186
ae821456cae552a9bb2fa1eabe44a964ec507c514aec7-ghc8.10.7.so)                                                                                                                                                                                                   

I built those on Fedora 34

@pepeiborra
Copy link
Collaborator

I built those on Fedora 34

This is why we distribute statically linked artifacts...

@hasufell
Copy link
Member Author

hasufell commented Feb 1, 2022

I built those on Fedora 34

This is why we distribute statically linked artifacts...

Huh? This isn't an issue at all. GHC distributes dynamically linked bindists as well, including for CentOS and some other distros. Yes, this will require building more than one linux bindist.

GHC CI has the code and infrastructure to do that.

@pepeiborra
Copy link
Collaborator

We could probably achieve the same thing with a matrix of containers in Github CI as well.

To deploy them, the VSCode extension should simply delegate to ghcup instead of doing a direct download as it does now.

The only thing missing here is a volunteer to drive this change. @hasufell what do you think?

@hasufell
Copy link
Member Author

hasufell commented Feb 2, 2022

We could probably achieve the same thing with a matrix of containers in Github CI as well.

Partly. Gitlab CI provides all architectures (including apple M1, FreeBSD 12/13 etc.). IMO, it is much easier to do this in gitlab. The emulation actions in github CI don't cover apple M1 and otherwise have massive memory constraints that we don't have in gitlab. FreeBSD action is lacking as well.

To deploy them, the VSCode extension should simply delegate to ghcup instead of doing a direct download as it does now.

Yeah

@hasufell
Copy link
Member Author

hasufell commented Feb 2, 2022

With the latest patch I improved the libdir detection (that's needed to point the binary to the correct GHC shipped dynamic libraries), so it will also work with stack.

  1. haskell-language-server-wrapper is statically linked (except for system libs) and doesn't need a wrapper script
  2. when haskell-language-server-wrapper is invoked, it uses the hie-bios functionality (getRuntimeGhcLibDir) to obtain the ghc libdir path of the project ghc (that also works with stack) and then sets GHC_LIBDIR environment variable before invoking haskell-language-server-8.10.7 for example
  3. haskell-language-server-8.10.7 is a wrapper script and will pick up said GHC_LIBDIR, add it to LD_LIBRARY_PATH and then exec the real binary

If someone invokes haskell-language-server-8.10.7 directly, it will:

  1. check if GHC_LIBDIR is set and use that
  2. otherwise invoke the newly added haskell-language-server-wrapper --print-libdir if it exists (that just prints the project GHC libdir and exits)
  3. fall back to ghcup whereis ghc <ver>
  4. then fall back to ghc-<ver> --print-libdir
  5. then fall back to ghc --print-libdir

I believe this is robust enough and most people will never experience a fallback beyond haskell-language-server-wrapper.

@jneira
Copy link
Member

jneira commented Feb 2, 2022

This setup is impressive and useful to help users get a hls binary which works for TH

However it will make more complex distribution channels, adding the Linux flavour to the actual combination.
It also adds makefiles not present in the repo until this.

I still hoped a better solution could come from upstream so I considered dynamic builds somewhat a temporary workaround and not sure if make deep changes will worth it

@jneira
Copy link
Member

jneira commented Feb 2, 2022

Want to note we are providing 37 artifacts in the last release

@hasufell
Copy link
Member Author

hasufell commented Feb 2, 2022

This setup is impressive and useful to help users get a hls binary which works for TH

It doesn't really help users, unless we distribute the resulting tarballs. The main Makefile is not meant to be run by users.

However it will make more complex distribution channels, adding the Linux flavour to the actual combination.

Sure. But the current distribution is broken, no?

I still hoped a better solution could come from upstream

Even if that happens, it will only work for very new GHC versions. Which means by the time a solution appears, it'll be 3+ years until it actually benefits most users.

@jneira
Copy link
Member

jneira commented Feb 2, 2022

It doesn't really help users, unless we distribute the resulting tarballs. The main Makefile is not meant to be run by users.

yeah, it will support such distribution, the motivation of the whole thing

Sure. But the current distribution is broken, no?

broken for some envs and no for others, today an user asked for a way to disable the warning cause th works for them out of the box

Even if that happens, it will only work for very new GHC versions. Which means by the time a solution appears, it'll be 3+ years until it actually benefits most users.
hi thanks.

sure, as always, and, as usual we could ask users upgrade if they want full th support in precompiled binaries (they always will be able to build from source, and with the ghcup compile helping)

@hasufell
Copy link
Member Author

hasufell commented Feb 2, 2022

Well, if you guys are against this approach, then I'd rather not invest more time into it.

@jneira jneira removed their request for review February 6, 2022 15:09
Copy link
Collaborator

@fendor fendor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, let's get this rolling!

Copy link
Collaborator

@michaelpj michaelpj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a lot going on here. It would be good to have a section in the documentation that explains what this is all for and why.

Can you also add yourself to CODEOWNERS for /bindist and any other new stuff.

@@ -0,0 +1,116 @@
UNAME := $(shell uname)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we put this somewhere other than the top-level, given that it really only exists to deal with the new bindist workflow?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer not to... otherwise the Makefile has to find the repository root or CI has to copy it back. It really belongs to the top level dir.

@@ -18,6 +18,7 @@ and it is being used in nix environments.

### prerelease sanity checks

- [ ] set the supported GHC versions and their corresponding cabal project-files in `bindist/ghcs` according to the [GHC version deprecation policy](../supported-versions.md#ghc-version-deprecation-policy)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is in the wrong section.

Is this really the only new step? Isn't there something that needs to be done with all these new mysterious makefiles?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These makefiles are used by gitlab CI to build the new bindists. They will replace the static bindists. I don't really understand the release documentation, so I'd appreciate someone with more knowledge can adjust it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the other hand, you understand the new stuff better than anyone else :) So if you don't want to integrate it into the existing docs, perhaps you could write a standalone explanation and then we can process that. But I think you've got to write us something :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will require follow-up PRs anyway to integrate better with the github actions workflows. I've never touched them so far, only the gitlab ones.

The static release builds via github actions will be obsolete and instead release artifacts will only be pulled from gitlab pipelines. That's not automated yet and might be done manually for now.

@hasufell
Copy link
Member Author

hasufell commented Feb 6, 2022

Alright, I'll try to re-iterate some of the discussion and motivation around this PR.

The problems

  1. The internal linker is used by HLS for building haskell code when it was linked with haskell libraries dynamically. But the real ghc binary will use the system linker. This may lead to issues on some systems with TH.
  2. There could be ABI mismatches between the internally linked ghc package and the one used by the system ghc binary, leading to segfaults and other issues. This problem exists for both static and dynamically linked flavours.

What this PR does

  1. It links haskell libraries dynamically, effectively forcing hls to use the system linker, just as the ghc binary does, potentially fixing some issues with TH.
  2. It checks for ABI mismatches between the internally linked ghc and the one the ghc binary uses and exits in case of mismatch. This check is currently performed in a wrapper shell script.
  3. It tries to reduce ABI mismatches by building the HLS binaries on more linux distros (synced with GHC CI) with ghcup provided GHC versions... instead of just alpine for all distros.

Drawbacks

  1. It requires some carefully crafted Makefiles to adjust RPATHs of resulting binaries and bundle the dynamic libraries with them.
  2. It requires a non-trivial wrapper shell script to find the correct ghc on start, because we need ghc-shipped dynamic libraries and add them to LD_LIBRARY_PATH.
  3. There's a minor chance that in esoteric set-ups, the ghc detected by the wrapper script is not the same as the one the project is actually built with (only when bypassing haskell-language-server-wrapper).

Alternatives

  1. We could do the ABI mismatch check directly in the hls exectuables
  2. We could continue to link haskell libraries statically, but build on many distros natively (dynamically linking against system libs). This would also reduce ABI mismatches, but not require the more complex distribution this PR introduces.

We don't need to guess the bin/ dir. Instead
we only need ghc libdir (which we already have)
and the full path to the ghc binary to reconstruct
the ghc wrapper.
@hasufell hasufell force-pushed the hasufell/PR/distribute-dyn-builds branch from 1e5c716 to ae442fb Compare February 6, 2022 23:33
@hasufell hasufell force-pushed the hasufell/PR/distribute-dyn-builds branch from 8d00d7f to db3bf2f Compare February 7, 2022 11:50
@hasufell
Copy link
Member Author

All green, let's go https://gitlab.haskell.org/maerwald/haskell-language-server/-/pipelines/47336

@pepeiborra pepeiborra merged commit a96b623 into haskell:master Feb 11, 2022
@jhrcek
Copy link
Collaborator

jhrcek commented Feb 14, 2022

This PR seems to have broken hls installation via ghcup from source.

I'm installing hls using ghcup compile hls -g master --ghc 8.10.7

Now when open my project in vscode, I'm getting this hls crash repeatedly:

Found "/home/jhrcek/Devel/github.com/Holmusk/PI/backend/hie.yaml" for "/home/jhrcek/Devel/github.com/Holmusk/PI/backend/a"
Run entered for haskell-language-server-wrapper(haskell-language-server-wrapper) Version 1.6.1.1 x86_64 ghc-8.10.7
Current directory: /home/jhrcek/Devel/github.com/Holmusk/PI/backend
Operating system: linux
Arguments: ["--lsp"]
Cradle directory: /home/jhrcek/Devel/github.com/Holmusk/PI/backend
Cradle type: Stack

Tool versions found on the $PATH
cabal:		3.6.2.0
stack:		2.7.3
ghc:		8.10.7


Consulting the cradle to get project GHC version...
Project GHC version: 8.10.7
haskell-language-server exe candidates: ["haskell-language-server-8.10.7","haskell-language-server"]
Launching haskell-language-server exe at:/home/jhrcek/.ghcup/bin/haskell-language-server-8.10.7
haskell-language-server-wrapper: user error (Pattern match failure in do expression at exe/Wrapper.hs:117:7-31)
[Error - 1:46:06 PM] Connection to server got closed. Server will not be restarted.

The pattern match failure error points to this line

(CradleSuccess ghcBinary) <- fmap trim <$> runGhcCmd ["-v0", "-package-env=-", "-e", "putStr =<< System.Environment.getExecutablePath"]

Am I doing something wrong, or should I file new issue for this?

@hasufell
Copy link
Member Author

This PR seems to have broken hls installation via ghcup from source.

I can't reproduce on my machine. Tried with both stack and cabal. I guess we need to fix the error reporting to figure out what's going on.

@fendor
Copy link
Collaborator

fendor commented Feb 14, 2022

@jhrcek I think we should open a new issue about it, hard to discover here

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

Successfully merging this pull request may close these issues.

7 participants