-
Notifications
You must be signed in to change notification settings - Fork 701
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
[RFC] Build Drivers for cabal #7906
Comments
This should probably be We could in principle also bundle shared libraries, they don't necessarily have to be static, there's just more work involved to get that right. A minor nit: |
Soft -1 because the proposal does not discuss how to integrate this with things that use cabal like:
|
@angerman -- note the proposal isn't for the syntax of that toml file, which is just given as an example. it is just for the @NorfairKing this is an extension to cabal files that Setup.hs will be taught about. Since afaik all those systems make use of Setup.hs, I believe no special integration with them is necessary, and your comment doesn't apply. |
+1 then :D |
@gbaz I understand. The libraries should still match up for readability. I've in fact been discussing this with @Kleidukos, and mentioned my disliking of the initial |
Hi, thank you for your feedback. @NorfairKing Yes I expect the integration with other tools to be transparent. :)
It was my understanding that Rust produced C-compatible libraries, so in my opinion we can call the archive "libCmylib.a".
Yes you are right it is confusing, I wanted to nod at the feature in Cabal but it doesn't have to be called like the Cabal stanza.
Could you expand a bit about it? |
To clarify, this is basically an isolated Why is
Isn't a build driver the same?
This is not necessarily true. A custom setup could simply consist of A remark about the hypothetical cargo driver: Cabal the library is supposed to just take a set of inputs and produce a set of outputs, and as you write should not manage dependencies. I think this should include Cabal's subprocesses. Cargo can download and manage its own dependencies, and this can be a huge pain for distributions, especially since the cargo call would be deep in the process tree and harder to control. For example, in nixpkgs, cargo packages are built by first vendoring all dependencies, hashing them, then telling cargo where to find them. If cargo is called by another tool, there is no way to do the last part without writing a cargo wrapper. It is usually better to let the "topmost" build tool / package manager (nix in my example) orchestrate all the others, and avoid "build tool -> package manager" chains. So I do at least partially agree with
By the way, in "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." what do you mean by "what other languages provide for free"? |
My understanding is that its a preconf hook in essence. The arguments aren't passed directly, so they can be in files, or they could not exist -- its outside of the scope. The problem with custom setups is they have access to all sorts of stuff and can configure or do anything -- they can reconfigure which modules are in the package or do lots of other stuff that affects later portions of the build. So custom setups require a fully different build path and produce packages whose basic metadata can't be statically known. This just runs some code without being able to affect other phases or feed anything back into cabal -- so its much more restricted. (btw, note the range of issues that we have with custom setups being just generally flakier and having corner cases compared to build-type: Simple -- https://github.com/haskell/cabal/issues?q=is%3Aissue+is%3Aopen+label%3A%22Cabal%3A+stanza%2Fcustom-setup%22%2C%22Cabal%3A+custom%22+) also see the motivation in the "What is needed to remove Custom build type" project: https://github.com/haskell/cabal/projects) |
Another advantage of this proposal is that |
@fgaz Elixir has something called Rustler: https://hexdocs.pm/rustler/0.18.0/basics.html / https://github.com/rusterlium/rustler It has enabled to simplify the integration of projects like html5ever: https://hex.pm/packages/html5ever
Yes it is a per-component basis. :) |
Yes, I'm not saying it isn't possible. Such a driver would work ok when used through For example, from the rustler docs:
This kind of option (especially the last two) would have to be patched out to integrate a package that uses rustler in most distributions, where the compiler is provided externally. As an analogy, calling But this is only relevant to the hypothetical cargo driver and not to this RFC, since wrapping a package manager can be done with current custom setups anyway. |
I'm confused, this is not for installing system-library equivalents, right? This is in the case where you build a hybrid haskell/rust application. Similar to how we have hybrid haskell/c applications today. If this is intended as a general "my application needs foreign library xyz; let's just pull that in", I'm going to be against that; that's what we have package management systems for, apt, rpm, ports, nix, homebrew, fink, ... If this is to enable hybrid applications in which parts are written in rust and others in haskell, I think this is a sane approach. We can't go the C route, unless we want to turn cabal into a rust solver and builder; that would be unadvisable IMO.
If such packages break distributions trying to package these up, they ought to figure out how to do this or work around it; arguing against this from a perspective of "but others won't be able to", is artificially limiting. I did mention that the rustup and absolute path in the rustler solution is nothing that should be permitted. The utility of this driver to me is to use external tools (rustc, cargo, ...) either on PATH or via environment variables, and encapsulate/abstract the build logic for a foreign library away from cabal, while still being able to tie the result back into the cabal package. Why are custom setups terrible to deal with? Because they are effectively blackboxes doing arbitrary logic. If this is a driver hook, I can decide to not run it and figure out what to do about it or not. If we have a proliferation of custom setups, and we don't want to run custom setups (for various legit reasons), we are worse off. This adds declarative logic, whereas setup.hs is just an arbitrary blackbox. In the former case, I can whitelist build-drivers I'm ok with, and prohibit anything else just from the cabal description. |
Yes, this is a discussion we can (and should) concern ourselves with at a later time. :) |
We agree.
Yes, thank you for the way you've put it, I think it's painting an accurate picture of what I had in mind.
And they can alter the configuration as well :|
Yup'. Generally I think you've outlined many relevant aspects this proposal. |
Enormous 👍🏻 for this. Even ignoring support for new programming languages, just having the option to use CMake or similar to build the C/C++ parts of a library with non-trivial foreign code included in it would be life-changing for me. |
I thought more about this and I'm now generally ok with the proposal. A distro can override a build driver simply by providing a package with the same name and that's nice and generic. My only fear is that this could turn into a second --though smaller-- Setup-like black box to be avoided. This is why I want the advantages to be clear. Also, since this would be such a big change for distros, I'd like to hear the opinion of some Haskell distro maintainer(s), such as @NixOS/haskell |
^ @peti @cdepillabout @expipiplus1 @maralorn @sternenseemann comments/opinions would be much appreciated! |
If these build drivers are supposed to be equivalent to preConf hooks, it would be perfectly possible for it to alter the cabal file ahead of configuration, wouldn't it? That would ruin the possibility to statically reason again.
Is this really a lot better? They still can execute arbitrary code, be arbitrarily configurable and keeping a list of good ones will be a problematic, as it's presumably easy to upload new ones to hackage and depend on them. |
I think this is too imperative. Just "please run this exe" sounds, frankly, like something that can only be used for evil. What I would like to see is some way to provide a custom ninja file or other lingua franca description of what yes,
but also,
This is a small step, but I think makes a huge world of difference in that people compelled to think about what is supposed to happen holistically. Really though, even this makes me umcomfortable. I really want more of things like This hasn't happened with Cabal/Cargo/etc. because they are.....big balls of imperative code and we don't really have a spec for their operational behavior!! This is the elephant in the room!! I assume based on the example that this feature is desired to make polyglot easier. Well, we can replace Conversely, if they all can spit out a mere plan we can then take those plans and combine them together, and execute them together. Actually compositional! To the extent we want to have the ninja extra rules thing I alluded to, it's because the plans are in fact higher order: external libraries, etc. are inputs that might be pre built, or may need to be built. Either way, the generated plan shouldn't care, it's just a parameter. |
@Ericson2314 Okay I can get behind the idea of spitting out an execution plan when using out-of-cabal builds, that's extremely reasonable and fair.
I understand how it sounds, factually that hasn't been the case in the Elixir ecosystem, but it's not unreasonable.
I'd be extremely grateful if you could provide me the desired level of details here. Let's say the endgame is to compile a rust project located in For example, when bundling libsodium, I have a list of things that I'm checking at build time: but while I know some things in advance (in my Setup.hs script), I also need the directory full of source. Should I aim to list them all, or would a directory path be enough? |
Let me start by saying how it is done with Nix.
In Rust and Haskell, there is also a temptation to vendor external C libraries for FFI libraries, and this royally fucks up up distro / Nix users! So please make the libsodium vendoring optional! I will grant that in this case the Haskell and Rust are clearly part of the same "project", not downstream FFI bindings, but projects are not packages. I would argue it's better to think of end projects (like some exe that, maybe, goes in a flatpak) as a mini distro unto itself. Anyways so back to the question, cabal2nix/haskell.nix will generate an parameter which we can then manually fill in (since there isn't a regular package that matches it per the record-pattern trick for "named parameters") and then everything works beautifully. The Rust is built, then the Haskell is is built, the builds are separate, so each language just needs to understand its own business, etc. etc.
What do you need the source for? |
Anyways I would consider wresting with some of the prior musings on these subjects outside of Nix too, to get a few more perspectives:
Meson is basically a reimaginaing of CMake. The idea is no version solving on one hand, but ergonomic / high level concepts and multi-language (CMake also is more recently) on the other hand. It's apples-to-organges vs For my part, I have written NixOS/rfcs#109 because the "lang2nix" things in Nixpkgs are current unpolished and only used by random downstream projects and not in Nixpkgs itself. I hope having much better, polished, and community-concensus based (rather than zillion experiments based) workflows for that will make the benefits of having Cabal/Cargo/etc. spit out complete command execution plans (vs plan.json or lockfile or something less "constructive") more enticing! |
CC @dcbaker who worked on Meson much more than me, since then also uses Nix, and might have some good musings to add on challenges for polyglot compositionality. |
I'll start with this: As a build system person I really appreciate getting rid of build.rs/build.hs/build.d, etc! These really are bad build system design. As someone who has dabbled in Haskell, I'm excited to hear that these are going away! I'll give my perspective, which is mainly from trying to mix Rust and C/C++ together, without cargo. I maintain most of the Rust support in Meson (and wrote much of it), for use in Mesa and the related free graphics ecosystem (our first public user of this was recently proposed). I've written kind of a lot, and I'm not sure how useful any of it is, lol. But here it is: So, from a build perspective. The problem of mixing languages like this, is, for correctness and performance, you really need one build system to drive the others. In Meson, we do this with CMake, by running CMake in a "print JSON of what you're going to do" mode, parsing that, and on the fly generating Meson assembly, then interpreting that. I've taken the same approach with cargo in Meson, though the version I currently have posted as a PR has some problems and needs to be rewritten. For cargo-in-meson, this results in (sometimes much) faster builds, as there is one tool managing the build graph (to ensure ordering) and managing the number of spawned jobs (to manage system load), instead of calling cargo as a command inside of Meson. I could keep going on the performance problems with having to have one builder wait on another, if that isn't clear.
In Meson we try to provide the tooling for both building your own copy, or using a system copy (because on different OSes there are different expectations, of course!) We call these wraps, and dependency requirements are allowed to "fall back" to a wrap if the normal set of finders (pkg-config, CMake, various -config tools like llvm-config, and some handwritten finders), and build your own copy. This allows us to make both *nix OSes happy (who expect you to use the system copy of zlib, for example) and to build on MacOS and Windows, where you're expected to provide your own copy. Just something to think about. I (and this is just my opinion) is that the future of building software is going to depend on having a build-plan standard, so that build systems can co-operate with each other seamlessly, there are massive C and C++ code bases that are never going to be rewritten in , meanwhile there are lots of exciting, useful projects being written in , each tightly bundling it's own package manager/build system hybrid together with the language. Meson's wrapping of cargo and CMake in Meson are fragile, and build.rs/build.hs are not good designs (Which I think most people who have dealt with such systems for any amount of time agree, including python who finally provided a mechanism to not use setup tools). Of course, that is hard, and require collaboration across a lot of project spaces. |
@Ericson2314 Thank you very much for the feedback. :)
I am not sure how you define "simple case", can't What would be a more complex case?
I'm not sure what's the takeaway from this blog post, the conclusion seems pretty grim for Meson if Cabal doesn't support Meson-the-language as an alternative syntax. :(
Okay that can be done (cc @kozross)
Okay so static vendoring would be better to activate when building a binary, whereas pkg-config resolution would be better to activate when simply building packages as part of hydra-or-something-like-that?
The piss-poor state of software packaging (and especially system dependencies) is what pushes me to adopt static vendoring for C libraries. Supporting and documenting the fantasies of five-to-six linux distros + macOS & the 3 major BSDs so that people can install a C library before compiling is not something I'm keen to do. Especially since not all distro repos are synchronized, so you have to make your end-user install a library version from official bindings when "stability" is promoted by the distribution. Unfortunately, making the end-user learn and maintain a nix environment for the sole purpose of providing a couple of C libs is not something I wish to impose on my packages' users (and while I already do that for myself, I don't have the resources to act as Nix support as well as support for my own project). |
@dcbaker thank you for this writeup, this is invaluable. :)
I think that such a standard could be fantastic, although I have no doubt it will be hard. But it if wasn't, we wouldn't be where we are today. :) |
Responding to @dcbaker
Just to be clear, I do agree, even though I nearly said the opposite. What I mean is I don't think any of the language-specific ones should drive the others, because it is unclear who should drive what, and we will just end up with some history-oriented (newer languages drive older) ad-hoc mes.
Yes wraps is very good! These wraps are subprojects --- Meson like Cabal has something like the |
@Ericson2314 I agree that in the context of meta-build systems it can be a problem, but on the other hand I can hardly see the Cabal team asking people to learn meson OR bazel OR nix (and these are not exclusive ORs :/). I agree in principle with the idea of wraps, though I haven't studied how they work. I would love some guidance for Cabal to be a "good citizen" for meta-build systems. |
I wasn't super clear on wraps I think, and they're one of the best features that Meson brings to the table, but they look like this: zlib = dependency('zlib') and then you have a "wrap file" which is an ini file (the key values might be wrong, (I'm on the top of my head): [wrap]
source=https://path/to/source/tarball
wrap_patch=https://path/to/meson/build/definitions ; optional, only required if upstream doesn't use meson
source_sha256=...
wrap_sha256=...
[provides]
zlib = <name of dependency object in meson build> ; only required to support old versions of meson, newer versions declare this in the build language Then meson will attempt to find the external dep with pkg-config, etc, and fallback if it can't. We also provide a command line option to force fallback (or error instead of falling back), to allow for finer grained control of that. Which allows distros wanting to package things ensure that they don't use wraps/subprojects, and end users who want things to Just Work™ to get that easily as well |
I ultimately do believe in the long term, it is better for language-specific build systems to say "learn Meson OR Bazel OR Nix". But I totally agree the onus is on those to get their shit together first. This is why I am pushing hard for NixOS/rfcs#109, Windows support, etc. These tools need to be really good before language-specific package managers will be comfortable not implementing features and telling their uses to use other stuff. It's a just a fact. In the short term, for e.g. the Haskell concurrency libraries I think "either build the C library yourself, or put up with evil Setup.hs, you pick your poison" is good enough. Inconvenience + beautiful compositionality vs convenience + big ugliness. Different people will find a tradeoff they prefer, and that buys us time,
So if we did have cabal project, with or without a
That would be great. I am not sure where the resources would come from to make this happy, but it would be wonderful to to. On one hand, getting non-Nix Cabal deps to look over the implementation of On the other hand, I think Meson will be for more attractive to people that have been put of with Nix's ambition to direct everything in excess of it's ability to polish things :). Meson is not "control the world", just like Cabal. and also works on Windows already, just like Cabal. If we could get a cabal dev to work with @dcbaker or another Meson dev and doing in Meson what was done for Cargo, I think that would also be a great learning experience, make something generally useful, and greatly inform any other efforts. |
Playing a devils advocate:
OTOH, if these can be fixed, the (With recent |
@phadej Thanks for bringing these up.
So a Meson integration project could actually help us gather good requirements for ghc-proposals/ghc-proposals#245 validated by the experiences of other languages! Also, I got NixOS/rfcs#92 now proposed for Nix, which is morally the same as the
|
It's a social problem, and I do believe it has a social solution! We have major Cabal and Meson contributors in this thread, and I gotten the occasional PR into Cargo. Getting everyone in a room together to hammer out a standard might not work, but if we build out some interopt incrementally I think it could. While the initial building trust and learning each others stuff is risky and uncertain, I do believe it's cheaper in the long run to, as less functionality is deduplicated, and barriers to people dabbling in the less popular languages (like Haskell :)) are removed. |
This is actually not pecular at all. Most languages require this at some level, either because an executable must be linked with the language implementing I also happen to personally know at least one major cargo maintainer, as well as a few CMake people (build systems are a small world). I'm happy to help build something on the meson side, either as a consumer or a producer (or both). |
Related, maybe duplicate: #4047 |
Does the new |
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:
Background
Historically, this function has been occupied by
Setup.hs
and theCustom
build-type, using user hooks like "pre-build". As we are now in a development phase of Cabal where we aim to get rid ofSetup.hs
, I would like to offer a clear and straightforward migration path for our users on this usage of the user hooks.Rationale
Example
Here is an example of a Cabal file that uses the future
cargo-driver
, to build Cargo packages with the Rust compiler: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:
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 stanzaQ: 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 pathto allow the transition from this
Setup.hs
use-case. One build driver can be analysed for n projects, but nSetup.hs
filesmust 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 beforecabal 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.The text was updated successfully, but these errors were encountered: