Just Build It - a "do what I mean" abstraction for Haskell build tools.
If you work with multiple Haskell projects, it can be annoying have to change gears mentally as to which set of tooling you have to work with for each one (configuring your editor, or even just the command-line).
jbi
aims to provide a common interface to the various Haskell build tools available and automatically determine which one you should use, so you can get back to hacking on your code, rather than on your environment.
Just Build It, and hack on!
A "do what I mean" abstraction for Haskell build tools.
Motivation
You've decided to work on an existing Haskell project. The repository has been forked, you've cloned it to your computer, and you're about to start work. What's the first thing you need to do?
Replace all copyright notices with your own name.
Swap all tabs and spaces.
Convert all the code to Literate Haskell because it's such a pain to write your long prosaic comments whilst remembering to preface every line with
--
.
Actually, unless you're someone with a religious obsession of using what you prefer no matter what project you're working on or who you're collaborating with, the first task you generally need to do is:
- Work out which build tool is being used in the project.
After all, especially as we tend to put in more and more metadata/hints into our different build tool files rather than just using runhaskell Setup.hs <foo>
, it's more convenient and friendlier to work with a project the same way everyone else (especially the maintainer!) does.
But this means you need to mentally switch gears and try and remember the quirks of each individual tool's command line configuration (how do I launch a REPL again?). Your editing environment may need to be configured so as to use the correct tool, whatever keyboard shortcuts you use to run tests needs to change, etc.
Wouldn't it be nice if there was a simple way your development environment (including your muscle memory!) could stay the same and let some common interface handle the changing (without falling into the trap of trying to replace everything)?
Enter jbi
jbi - short for "Just Build It" - is aimed at providing a common interface between the various Haskell build tools. You should be able to enter any directory containing a Haskell project and just run jbi
and it will successfully determine the best build tool to use, download dependencies and build the project.
Currently, jbi knows of the following Haskell build tools:
stack
(with automatic [Nix] support)cabal-install
with [Nix] support (usingcabal2nix
andnix-shell
)cabal-install
using sandboxes
Note that nothing within jbi is inherently Haskell-oriented; it can be extended to any build tool for any language which has similar concepts for build tooling.
How jbi works
To determine which build tool to works, jbi takes into account three things:
The order in which the tools are available to be checked in (currently the same as in the list above).
Whether a build tool is able to be used (i.e. the tool is installed and an appropriate project can be found).
Whether it is already being used.
Preference is given to tools already in evidence of being used. As an example, consider the following scenario:
myProjectDir/ $ ls
cabal.sandbox.config LICENSE myProject.cabal src/ stack.yaml
If both cabal-install
and stack
are available, then - despite the presence of a stack.yaml
- the presence of a sandbox configuration indicates that a preference has been made for using them.
Features
Automatically install dependencies for and enable test-suites and benchmarks.
Attempt to re-configure (including installing dependencies) if builds fail (which
stack
already provides)The equivalent of
cabal run
forstack
.Print out a list of targets (equivalent of
stack ide targets
, for whichcabal-install
does not have an analogue).Detailed debugging information about tool availability.
Work within any sub-directory of a project (no need to make sure you're running from the root directory!).
Caveats
jbi will not:
Generate a
stack.yaml
for you. This is an explicit opt-in of wanting to usestack
, and furthermore isn't possible to determine whether you want it just for the current package or if it's part of a larger multi-package project.Install the result of the build for you. jbi is purely for developmental purposes.
Allow you to not build the test suite or benchmarks (unless you specifically build a specific target).
Allow you to have flexible builds, pass through extra command-line options, etc. It is opinionated in how it does things to cover the common cases.
Furthermore:
- I have only recently started using [Nix] (both with Stack and cabal-install) and as such may not have it quite right (it seems to work with me though).
Fortuitously Anticipated Queries
Why isn't my build tool of choice being used?
Run jbi info details
to find the information being used to choose the build tool. The chosen build tool will have:
"installation"
non-null."usable": true
- A non-null
"project"
Preference is given to:
- Build tools with
"artifactsPresent": true
- Higher up in the list.
What are these artifacts?
"Artifacts" is the term used by jbi to denote the build-tool specific files/directories found within the project that indicate it is being worked upon.
These are:
stack ~ .stack-work/
cabal+nix ~ shell.nix
or default.nix
cabal+sandbox ~ cabal.sandbox.config
(note that the sandbox itself may be in a different directory)
jbi prepare
will generate these; jbi clean
will remove them (with any other files/directories likely to have been produced as part of the build process). Typically you will never need to explicitly run jbi prepare
.
Stack doesn't seem to be using Nix
For [Nix] support to work, you need to configure your stack.yaml
.
Why can't I use Stack with shell.nix?
For a project with no .stack-work/
, jbi takes the presence of a shell.nix
file to indicate that the project is using cabal+nix, irregardless as to whether a stack.yaml
file is present.
There are two ways you can work around this:
Explicitly create a
.stack-work/
directory; as stack has a higher priority, jbi will then pick it over cabal+nix. Note, however, you may also need to explicitly runstack setup
if using a non-system GHC.Use a different filename other than
shell.nix
(remember to specify the filename properly in theshell-file
section!).
The latter is preferred as it will allow more of jbi's automatic features to work (e.g. calling stack setup
).
How can I re-generate my shell.nix after updating my .cabal file?
For cabal+nix.
Run jbi prepare
. This is likely the only scenario you will ever need to explicitly run this command in.
Why don't I have benchmarking support with cabal+nix?
Benchmarking using cabal+nix requires support from nixpkgs
. This is currently present in the unstable
branch but is not yet present in a release (but should hopefully be found in 18.03
).
You can verify whether your version of nixpkgs
supports benchmarking Haskell code with:
nix-instantiate --eval --expr 'with import <nixpkgs> {}; haskell.lib ? doBenchmark'
Note that jbi currently doesn't support specifying which channel you are using and defaults to nixpkgs
. If you are using unstable
you can try to manually configure by editing the generated shell.nix
and replacing <nixpkgs>
with <unstable>
(or whatever you have called that channel) and running:
nix-shell --arg doBenchmark true \
--run 'cabal configure --enable-tests --enable-benchmarks'
How do I add a new build tool?
Pull requests are welcome.
To add a new tool, you need to create an instance of the BuildTool
class from System.JBI.Commands.BuildTool
, and then insert your new tool into an appropriate place in defaultTools
in System.JBI
.
What about languages other than Haskell?
If, for some reason, you wish to use a language other than Haskell and would like to use jbi with it, you're more than welcome to send me a pull request.