Skip to content

[RFC] Build Drivers for cabal #7906

Open
@Kleidukos

Description

@Kleidukos

Summary

The proposal is about the creation of a new stanza in the cabal file format: build-drivers. This stanza takes a list of executables that run before the build of the Cabal components, in order to provide any required generated Haskell code, foreign C libraries, etc.
The roles of building foreign packages are distributed as such:

  • Dependencies: Left to the build system with which the build driver interacts
  • Calling other build systems: This is what the build driver does
  • Linking: This part is supported by Cabal & GHC when it comes to C-style interfaces, and provides a wide enough coverage of FFI candidates.

Background

Historically, this function has been occupied by Setup.hs and the Custom build-type, using user hooks like "pre-build". As we are now in a development phase of Cabal where we aim to get rid of Setup.hs, I would like to offer a clear and straightforward migration path for our users on this usage of the user hooks.

Rationale

  • This helps us get rid of Setup.hs in a clear and predictable way
  • We can remove some less-used logic from cabal-install when it comes to custom preprocessor, and don't have adopt new ones should they emerge (WASM, JS, etc)

Example

Here is an example of a Cabal file that uses the future cargo-driver, to build Cargo packages with the Rust compiler:

library
    hs-source-dirs:     src
    default-language:   Haskell2010
    ghc-options:        -Wall
    build-depends:
        base >=4.10.0.0 && <5,
        bytestring >=0.11.0.0
    data-files:
      native/rust-library
    extra-bundled-libraries:    rustLibrary -- the rust library's C interface that we are building with the cargo driver
    build-drivers:
      cargo-driver -- cargo-driver then parses a TOML file that holds its necessary configuration
# Configuration of the cargo build driver
# Every one of those options is over-ridable by environment variables
# But this is in the cargo-driver logic, not in Cabal

# rustLib.a will then be put in the cabal build directory so that Cabal can statically link to it.
extra-bundled-library = "rustLib.a"

[[cargo-projects]]
  path = "native/rust-library"
  mode = "release" # or "debug"
  default-features = true
  features = ["feature1", "feature2"]

Challenges

Since there won't be any information passing between Cabal and the build drivers, some preliminary data should written on file in order to make this all work. Especially, the precise location of the build directory that needs to contain the .a is something to have.

Community feedback

Having taken the time to survey members of the community beforehand, I took their feedback into account and have reported their questions:

These are real questions I've received, if you have more that I haven't answered here, please ask them to me.

Q: Cabal is going to have to learn how to configure rust/zig/D/C++/meson/bazel projects?

A: No. The logic is offloaded to build drivers (like cargo-driver) that get the necessary information from a custom stanza


Q: Cabal already knows how to do it, it's called Setup.hs

A: Setup.hs' unrestricted code execution proves to be a liability. The ability here is to provide a clear and official path
to allow the transition from this Setup.hs use-case. One build driver can be analysed for n projects, but n Setup.hs files
must be analysed for n projects.


Q: This cargo-driver of yours, who's going to be in charge?

A: A healthy mix of stakeholders and community members, the idea being that some central coordination with the cabal team is needed and we can't have this left to the good will of the community altogether, because we want to present a coherent and reliable "Rust integration" story. The Haskell Foundation can have a stake in it, for instance. But anybody can make a driver, it's not a walled-garden. cargo-driver will be maintained in a more “official” capacity due to its ecosystem importance and significance.


Q: This is too heavy/rigid for me, why can't we just let the user figure it out?

A: The goal is to offer a clear and straightforward path to native code integration without having to retain the knowledge of those systems in Cabal. At the same time, more flexible tools are an open door to unregulated code execution.


Q: This is the wrong approach, you should put that knowledge in something higher-level like Bazel

A: This doesn't prevent you from using Bazel but forcing people to learn Bazel in order to do what other languages provide for free is asking too much for too few benefits.


Q: You're bloating Cabal, external dependencies shouldn't be necessary to use external dependencies

A: We think that the weight of having a build driver as a compile-time dependency to handle other build dependencies is an acceptable compromise.


Q: Realistically, what system only knows how to do cabal build? Can't you run a command before cabal build that takes care of everything?

A: Not every system is able to run arbitrary command when compiling a Haskell package. More specifically each time a package X depends on a package Y, cabal will build the dependency with a standard cabal build. Build platforms that support cabal shouldn't have to adapt to something too flexible when it comes to building cabal packages.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions