Disclaimer
The purpose of this and the following posts is to evaluate options for build automation tools for Haskell, and propose possible solutions for improving developer experience and facilitating test-driven development.This post is solely based on my opinion, which is affected by my experience with other tools, mostly Maven and Make. I believe that having a good build automation tool makes a big impact on developer experience and code quality. I think improving build automation is easier and more beneficial than enabling continuous integration using a faulty build tool.
I don't have an intention to criticize anyone or the software they are contributing to. I think Cabal is a great tool for creating and sharing packages, but I would like to look at it in relation to build automation.
What is a `build automation tool'
Wikipedia on build automation:Build automation is the act of scripting or automating a wide variety of tasks that software developers do in their day-to-day activities including things like:
compiling computer source code into binary code
packaging binary code
running tests
deployment to production systems
creating documentation and/or release notes
Using Cabal for builds
Packaging and sharing of Haskell projects is traditionally done via Cabal. Cabal is as good as it can get for manipulating Haskell packages and interacting with HackageDB, but it has several shortcomings when it comes to manging builds.
There is an excellent post about why Cabal is not a good package manager. I have to add that it might not pass as a modern build automation tool, either.
Build life cycle
Build life cycle is a set of stages that project passes during build process. It enforces a work flow. For tools that see builds as a set of custom tasks or targets (Make, Rake, Ant) that work flow is defined by the relationships between tasks and there is now default. For tools that are based on conventions and standard life cycles, like Maven, there is a default work flow that can be extended to fit project needs.
Cabal does not fall into either of the categories: it is does not have custom tasks, but does not have a standard work flow either. There is a list of commands in Cabal user guide, (and some of them are obviously included in one another like install and register) but there is no clear definition of what build phases project has to go through before deployment.
Configuration
In addition to .cabal configuration file, there is an extra `configure' step that sometimes needs to be run manually. By default `cabal configure' checks if the dependencies are installed, but it has to be used for some other things, like enabling tests (more on that later):cabal configure --enable-tests
cabal build
cabal testI am not sure why that could not have been read from .cabal and automatically included in the build or test phase.
Also, Cabal configuration can not define new build phases, cabal will always stick with the set of command it already has. But there are various situations when having adding a new goal makes a lot of sense than trying to integrate with existing ones.
Haskell Dependencies
There are some issues with how cabalized dependencies are handled in Cabal builds.Local build dependencies are optional
Unless you are using sandboxes, dependencies that Cabal installs are fairly global (system-wide or user). That can cause version conflicts which you might not be able to resolve.The solution is to use optional cabal sandbox, that will track installed dependencies for the current copy of the project separately from system-wide libraries. That would help, but will add a few extra manual steps, which will bring us to the next point.
Too much manual intervention
To tell cabal to install dependencies and then build one needs to run two commands:cabal install --only-dependencies
cabal build
That installs dependencies globally, so if you want to tie them your sandboxed version of the project, you would have to run a few `cabal sandbox' commands on top of that. Sandboxed and non-sandboxed dependency installation is done by the same command, so if you forgot to create a sandbox you are risking messing up your Haskell installation.
Testing
In order to add tests to a cabalized project, one has to provide wrapper code, for example you might have to surround your quickcheck test suite with IO code that reports status via exit codes.Also, testing is optional, to enable it one has to run a configure command before building the project:
cabal configure --enable-testsThis has to be done even after you have included a reference to test suite in your .cabal configuration file (just mentioning it in the configuration file is not enough). Also, `enable tests' does not stick, if you re-run configure and forget the flag, Cabal will happily disable tests. None of that helps with test-driven development.
TL;DR
Cabal is a great tool for manipulating packages, but it does not enforce a build work flow and requires a significant amount of manual bookkeeping to produce a full build.(To be potentially continued...)
No comments:
Post a Comment