A "do what I mean" abstraction for Haskell build tools.
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)?
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.
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.
-
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!).
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).
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.
"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
.
For Nix support to work, you need to configure your
stack.yaml
.
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
).
For cabal+nix.
Run jbi prepare
. This is likely the only scenario you will ever
need to explicitly run this command in.
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'
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
.
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.