Skip to main content

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 ()
main = TIO.putStrLn "Script me!"

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 ()
main = TIO.putStrLn "Script me!"

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.

Author

Travis Cardwell

Published

Tags