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 (using cabal2nix
and nix-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
for stack
.
-
Print out a list of targets (equivalent of stack ide targets
, for
which cabal-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 use stack
, 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
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 run stack setup
if using a
non-system GHC.
-
Use a different filename other than shell.nix
(remember to
specify the filename properly in the shell-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'
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.