google / hrepl

Interactive development for Bazel/Haskell rules
Apache License 2.0
47 stars 10 forks source link

Feature request: Only print GHCi flags without opening the REPL #11

Open aherrmann opened 4 years ago

aherrmann commented 4 years ago

Feature

Add an option for hrepl to only print the relevant flags to GHCi for loading the specified range of targets. This should probably exclude flags that are only required for hrepl specifics, e.g. for the ghci script that hrepl defines.

Use-case

The use-case I have in mind is to use hrepl in a hie-bios cradle to enable ghcide to load rules_haskell projects.

hie-bios has a bios cradle that takes a program that will write all required GHC flags to a file specified by the $HIE_BIOS_OUTPUT environment variable. hrepl could be used as that program. Furthermore, hie-bios could be extended with a dedicated Bazel cradle (reviving the currently disabled one).

An example use-case could look like this:

cradle:
  multi:
    - path: "./src"
      config: { cradle: {bazel: {targets: ["//src/..."]}} }
    - path: "./test"
      config: { cradle: {bazel: {targets: ["//test/..."]}} }

Assuming wild-card support in hrepl (or the bazel cradle) and multi-cradle support in ghcide.

jinwoo commented 4 years ago

Unfortunately hie-bios recently removed bazel support.

We internally have some patches to make hie-bios and ghcide work in our build environment (a bazel variant). We're not ready yet to publish them but I can give a high-level description.

Hopefully this helps. Please feel free to ask if you have questions.

aherrmann commented 4 years ago

@jinwoo Thanks for the detailed response and sorry for the late reply.

Unfortunately hie-bios recently removed bazel support.

I'm aware, though the possibility to add it back is explicitly spelled out in the corresponding comment.

We internally have some patches to make hie-bios and ghcide work in our build environment (a bazel variant). We're not ready yet to publish them but I can give a high-level description.

Thanks for sharing those insights.

  • hie-bios

    • We create "implicit" cradles rather than depending on hie.yaml files so that ghcide can work out of the box
    • Each bazel build target becomes an implicit cradle with its own GHC flags and source files
    • Given a Haskell source file, it finds the corresponding bazel target and creates a cradle for it

I'd be curious to learn more about how this works in practice. Let's consider a concrete example to ease the discussion.

haskell_toolchain_library(name = "base")
haskell_library(
    name = "lib",
    srcs = ["Lib.hs"],
    deps = [":base"],
)
haskell_binary(
    name = "bin",
    srcs = ["Main.hs"],
    deps = [":base", ":lib"],
)

If I understand you correctly, then :bin will be loaded into a separate cradle than :lib. This would mean that :lib needs to be compiled and will be loaded as a prebuilt package into the GHC session for :bin, correct? This, in turn, would mean that source changes to :lib are not automatically picked up by the session for :bin. So, the user would have to reload the IDE to make those visible. How do you address this issue in your internal forks?

  • ghcide

    • There are quite a few patches here but the most important patch was to NOT locate imported files from random locations in the file system. Because bazel is hermetic and all the dependencies are explicitly specified in the build targets, when a module imports another module, it should be located only from those specified dependencies

Does this require ghcide to talk to Bazel directly, or were you able to implement this maintaining the hie-bios abstraction?


As for this feature request, I think it would still be a useful addition. We've just added a similar feature to rules_haskell which allows pointing ghcide at a haskell_repl target. The same should be possible with hrepl.

jinwoo commented 4 years ago
haskell_toolchain_library(name = "base")
haskell_library(
    name = "lib",
    srcs = ["Lib.hs"],
    deps = [":base"],
)
haskell_binary(
    name = "bin",
    srcs = ["Main.hs"],
    deps = [":base", ":lib"],
)

If I understand you correctly, then :bin will be loaded into a separate cradle than :lib. This would mean that :lib needs to be compiled and will be loaded as a prebuilt package into the GHC session for :bin, correct?

Correct.

This, in turn, would mean that source changes to :lib are not automatically picked up by the session for :bin. So, the user would have to reload the IDE to make those visible. How do you address this issue in your internal forks?

Unfortunately we don't have a good solution yet. Some editor plugins support LSP server restart and it helps a bit, but yeah it is annoying. Our hope is that once ghcide supports .hie well, it could improve this but we're not sure.

Another problem is "Go to definition" doesn't work across multiple build targets due to the same reason.

  • ghcide

    • There are quite a few patches here but the most important patch was to NOT locate imported files from random locations in the file system. Because bazel is hermetic and all the dependencies are explicitly specified in the build targets, when a module imports another module, it should be located only from those specified dependencies

Does this require ghcide to talk to Bazel directly, or were you able to implement this maintaining the hie-bios abstraction?

In order to find the imported module only from the source files specified in the build target, we set the "targets" of the current GHC session to those files. I have a (rejected) upstream PR: https://github.com/digital-asset/ghcide/pull/394. And we patched the locateModuleFile function so that it locates the file only from the current "targets", rather than from any files in the file system.

For other things, we also have patches for hie-bios, where we use bazel query to get various information. For example, given a source file, to find the corresponding build target. They are done by the hie-bios layer, so ghcide doesn't directly talk to bazel. It only knows about the (bazel) cradle.