Skip to content

How to handle conditional/optional dependencies? #794

@wz1000

Description

@wz1000

This is in the context of ghcide, but is more of a general query on how to accomplish this sort of thing with shake, so I'm asking here.

A bit of background first -

  • Compilation via GHC can be run in two modes, either we can just typecheck, producing a ModIface, or we can go all the way and do code-gen, producing a Linkable.
  • This process can't be resumed - we can't go from a ModIface to a Linkable, we need to redo the entire compilation pipeline in order to produce a Linkable
  • We only need Linkables for modules that are depended on by something that uses TemplateHaskell
  • So, we have a rule called NeedsCompilation which analyses the the reverse dependencies of a module to check if we need to do code-gen. It returns True if this is the case, or False if we can stop after generating the ModIface.
  • The GetModIface rule generates a pair (ModIface, Maybe Linkable), where the invariant is that the Linkable is guaranteed to be present if NeedsCompilation returned true. (NeedsCompilation is a dependency of GetModIface)

All this seems to have been working fine. However, the complication is introduced with the eval plugin.

To evaluate an expression in the context of a module, we must have Linkables for it and all of its dependencies.

A first approach at supporting this can be:

  1. When evaluating an expression in the context of a file, somehow mark the file as needing compilation in some global state.
  2. Make NeedsCompilation consult this bit of global state when it makes a decision.
  3. Restart the shake build
  4. If all went well, we would have all the Linkables we require, and we can evaluate the expression
  5. Reset the global state so we don't do unnecessary code-gen on subsequent builds.

This looks reasonable at first, but it means we will just throw away all the Linkables we computed for eval on subsequent builds after evaluation, and we will have to rebuild everything, since NeedsCompilation changed from True to False, and it is a dependency of GetModIface.

Ideally, as long as all dependencies of GetModIface except NeedsCompilation are unchanged, we would not need to recompute the ModIface (The extra Linkable doesn't really hurt, and it could be used by subsequent evaluations). If some dependency of GetModIface other than NeedsCompilation did change, then we want to do a rebuild, but without producing a
Linkable.

Also, we only want to have this behavior when NeedsCompilation flips from True to False. If it flips from False to True, then we really do need to rebuild.

While going through the shake documentation, I came across the ChangedStore constructor of RunChanged. Its documentation reads "The stored value has changed, but in a way that should be considered identical (used rarely)." which does seem like it somewhat fits this situation. However, I'm still not sure if this is its purpose, and if using it would work in this scenario.

Is something like this possible with the shake model? Is ChangedStore the way to accomplish this?

/cc @pepeiborra

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions