Cabal Scripts
Haskell Discourse discussion “An
external command system for cabal: what would you do with it?” is a
call for ideas about how to use a new command system for Cabal. There is a Cabal
feature that I have wanted for a long time: the ability to run
single-file scripts that can use packages from Hackage, like Stack’s stack script
functionality. I checked the Cabal documentation
to make sure that the functionality has not been added, and I discovered
that it has been added to the cabal run
command!
The last time I checked, Cabal could run single-file scripts, but
there was not a way to use packages (aside from base
). This
significantly limited the usefulness of the command, as most scripts I
write have other dependencies. I have been writing such scripts using
Stack, but that is not convenient for people who do not use Stack. For
example, I wrote a script to generate some test data for work earlier
this week, and we use Cabal at work.
Scripts can support more than one version of GHC and specify
dependency version constraints like in .cabal
files. The
following is a minimal example:
#!/usr/bin/env cabal
{- cabal:
build-depends:
base >=4.14 && <4.19
, text >=1.2 && <2.1
-}
{-# LANGUAGE OverloadedStrings #-}
module Main (main) where
-- https://hackage.haskell.org/package/text
import qualified Data.Text.IO as TIO
main :: IO ()
= TIO.putStrLn "Script me!" main
Alternatively, scripts can specify the version of GHC to use. In this
case, the ^>=
operator is a convenient way to specify a
narrow range of compatible versions of dependencies. The following is a
minimal example:
#!/usr/bin/env cabal
{- cabal:
build-depends:
base ^>=4.18
, text ^>=2.0
-}
{- project:
with-compiler: ghc-9.6.2
-}
{-# LANGUAGE OverloadedStrings #-}
module Main (main) where
-- https://hackage.haskell.org/package/text
import qualified Data.Text.IO as TIO
main :: IO ()
= TIO.putStrLn "Script me!" main
Note that it is different from how Stack scripts are configured, because specifying a Stack resolver determines the versions of dependencies that are included in that snapshot. One only has to specify version numbers of packages that are not in the snapshot. With Cabal, one can of course specify exact dependency versions for such reproducibility.
Cabal builds scripts in the $HOME/.cabal/script-builds
directory. These builds are not deleted automatically, so that scripts
do not need to be rebuilt when they are run again. You may therefore
want to occasionally clean/purge old builds manually.
I am very happy to discover that Cabal now has this functionality! These features are exactly those that I wanted.