Description
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.