cartel- Specify Cabal files in Haskell

Safe HaskellSafe-Inferred




Cartel - a library to specify Cabal files in Haskell

The Cabal file format works very well for small projects. However, in big projects with a library, many executables, and test suites, some irritations emerge. You need to specify dependencies in multiple places, leading to redundancy. You also have to manually add in new modules, make sure you list all modules (a real pain with executables, as problems may arise only after you build a distribution tarball), and update your module lists when you refactor.

Specifying your Cabal files in Haskell rather than in a plain-text file format helps deal with a lot of these problems. You have the full power of Haskell to make definitions in one place and then reuse them. You can also dynamically read a tree of modules and use the result, thus avoiding the need to manually update your module lists.

A disadvantage to Cartel is that is more verbose than a vanilla Cabal file. In addition, you also have to remember to generate the new Cabal file whenever you change the script that generates your Cabal file.

To the extent possible, Cartel uses the Haskell type system to prevent you from making mistakes in your Cabal file. For example, the Betsy type prevents you from using flags that you have not declared, and you can't put an exposedModules field in anything but a library. However, Cartel does not prevent against all errors. For example, Cartel does nothing to prevent you from applying a function that calls for a NonEmptyString to a string that is, in fact, empty. Another example is that Cabal requires executables to have a main-is field, but Cartel does not force you to include one. Ultimately your Cabal file might still have errors that you have to fix by changing the program that generates the file.

Everything you usually need is in this module. Other Cartel modules contain implementation details. See first the NonEmptyString type synonym, which has important details on how to regard Strings and NonEmptyStrings as you read the documentation. Also, examine Cartel.GenCartelCabal, which generates Cartel's own Cabal file using Cartel.

Hopefully this module's documentation is organized so that top-to-bottom reading will make sense.


Basic types and classes

data Word :: *

A Word is an unsigned integral type, with the same size as Int.

type NonEmptyString = String Source

A non-empty string. This string should never be empty. It is used where, for example, a field in a Cabal file is required to have a value and that value cannot be empty. In contrast, Cartel uses an ordinary String for values that can be empty.

This is only a type synonym, so nothing in the type system enforces that these strings must be non-empty. Typically though, Cabal will give you grief about the file that Cartel generates if you used an empty value for a NonEmptyString.

class Blank a where Source

Typeclass for things that can be blank. More specifically, blank a results in an item that, when rendered in a Cabal file, is the null string. blank can be useful to indicate that you have no options, and you can also use it in combination with record syntax when you want to specify just a few options.


blank :: a Source

type Version = [Word] Source

A version number. The Cabal documentation says this "usually" consists of a sequence of natural numbers separated by dots. Though this suggests that a version number could contain something other than natural numbers, in fact the types in the Cabal library do not allow anything other than numbers and you will get a parse error if you try to use anything else.

Therefore Cartel's Version type only allows a list of Word, as each number cannot be negative. In addition, this list should never be empty. However, this is just a type synonym for a list of Word, so the type system does not enforce the requirement that this list be non-empty.


data Section Source

A single section in a Cabal file; this may be a source repository, executable, test suite, or benchmark. You build a Section with the repository, executable, testSuite, and benchmark functions.


Version control systems

data Vcs Source

Version control systems.


cvs Source


:: NonEmptyString

The named module

-> Vcs 

Repository kinds

data RepoKind Source

What kind of VCS repository is this?

Building repositories

data Repository Source

A single repository section.




repoVcs :: Maybe Vcs

What kind of Vcs this is. This is required.

repoKind :: Maybe RepoKind

The kind of repository (repoHead or repoThis). Required.

repoTag :: String

Repository tag. This is required for the repoThis repository kind and is optional for the repoHead repository kind.

repoLocation :: NonEmptyString

URL for the location of the repository--for example, for a darcs repo this might be; for git, this might be git://

repoBranch :: String

The repository branch.

repoSubdir :: String

The repository subdirectory.

githubHead Source


:: NonEmptyString

The Github account name

-> NonEmptyString

The Github project name within the account

-> Section 

Creates a Section that is a Repository for a Github head. For example, for Cartel, use githubHead "massysett" "cartel".

repository :: Repository -> Section Source

Creates a Section for a repository.


class LogicTree a where Source


(&&&) :: a -> a -> a infixr 3 Source

(|||) :: a -> a -> a infixr 2 Source

invert :: Condition -> Condition Source

Like not, which is what I would have named it but for the conflict. Only Conditions have this sort of operation; Cabal does not have a (documented, at least) way to express this for package constraints.


data Constraint Source

Expresses any version constraint, including no version constraint.

lt :: Version -> Constraint Source

Less than

gt :: Version -> Constraint Source

Greater than

eq :: Version -> Constraint Source

Equal to

ltEq :: Version -> Constraint Source

Less than or equal to

gtEq :: Version -> Constraint Source

Greater than or equal to

anyVersion :: Constraint Source

Matches any version at all (in a Cabal file, this is represented as an empty string).


data Condition Source

Conditions. Ultimately these are used in a CondBlock.

system :: NonEmptyString -> Condition Source

Operating system; tested against System.Info.os on the target system.

arch :: NonEmptyString -> Condition Source

Argument is matched against System.Info.arch on the target system.

impl :: Compiler -> Constraint -> Condition Source

Tests for the configured Haskell implementation.

flag :: FlagName -> Condition Source

Evaluates to the current assignment of the flag of the given name. Flag names are case insensitive. Testing for flags that have not been introduced with a flag section is an error.

true :: Condition Source

Always true.

false :: Condition Source

Always false.

condBlock Source


:: HasBuildInfo a 
=> Condition

Condition to satisfy

-> (a, [a])

Use these results if condition is true

-> [a]

Use these results if condition if false

-> a 

Builds if statements. For example:

condition (flag "buildExe") (buildable True, []) [buildable False]

A little more complicated:

condition (flag "buildExe" &&& system "windows")
  (buildable True, []) [buildable False]


data Package Source

A single package, consisting of a package name and an optional set of constraints. Used when specifying buildDepends, buildTools, and pkgConfigDepends.

Some functions exist to ease the creation of a Package. For a package with no version constrains, simply do something like unconstrained "QuickCheck". Common use cases are covered in the functions in the "Package Helpers" section below. For something more complicated, use the functions in the "Logicals" sections above, along with the &&& and ||| combinators, to create your own Constraint and then use it with the package function.

package Source


:: NonEmptyString

The name of the package

-> Constraint

Version constraints.

-> Package 

Builds a Package.

Package helpers

closedOpen Source


:: NonEmptyString

Package name

-> Version

Version number for lower bound

-> Version

Version number for upper bound

-> Package

Resulting Package

Creates a package interval that is closed on the left, open on the right. Useful for the common case under the PVP to specify that you depend on a version that is at least a particular version, but less than another version.

closedOpen "bytestring" [0,17] [0,19] ==> bytestring >= 0.17 && < 0.19

apiVersion :: NonEmptyString -> Version -> Package Source

Specifies a particular API version. Useful to lock your package dependencies down to a particular API version.

apiVersion "base" [1] ==> base >= 1 && < 2
apiVersion "base" [1,2] ==> base >= 1.2 && < 1.3
apiVersion "base" [1,2,3] ==> base >= 1.2.3 && < 1.2.4

nextBreaking :: NonEmptyString -> Version -> Package Source

Depends on the version given, up to the next breaking API change.

nextBreaking "base" [4] ==> base >= 4 && < 4.1
nextBreaking "base" [4,1] ==> base >= 4.1 && < 4.2
nextBreaking "base" [4,7,0,0] ==> base >= && < 4.8

nextMajor :: NonEmptyString -> Version -> Package Source

Depends on the version given, up to the next time the first digit increments. Useful for base.

nextBreaking "base" [4] ==> base >= 4 && < 5

exactly :: NonEmptyString -> Version -> Package Source

Depends on exactly this version only.

exactly "base" [4,5,0,0] ==> base ==

unconstrained Source


:: NonEmptyString

Name of package

-> Package 

Allows any version of a package.

Build information

Libraries, executables, test suites, and benchmarks all share common fields for build information. BuildInfoField represents these common fields, and HasBuildInfo is a typeclass encompassing libraries, executables, test suites, and benchmarks. You can build these fields for any of these sections using the functions and values listed here.

data BuildInfoField Source

A single field of build information. This can appear in a Library, Executable, TestSuite, or Benchmark.

class HasBuildInfo a Source

Things that can be an item in a build information field in a Cabal file.

Minimal complete definition

conditional, buildInfo

haskell98 :: HasBuildInfo a => a Source

Sets Haskell 98 as the default-language.

Currently not documented in Cabal, see

haskell2010 :: HasBuildInfo a => a Source

Sets Haskell 2010 as the default-language.

Currently not documented in Cabal, see

buildDepends :: HasBuildInfo a => [Package] -> a Source

Modules used but not exposed. For libraries, these are hidden modules; for executable, these are auxiliary modules to be linked with the file in the main-is field.

modules can help greatly with maintenance of this field.

otherModules :: HasBuildInfo a => [NonEmptyString] -> a Source

Root directories for the module hierarchy

hsSourceDirs :: HasBuildInfo a => [NonEmptyString] -> a Source

Haskell extensions used by every module.

buildTools :: HasBuildInfo a => [Package] -> a Source

Programs needed to build this package, such as c2hs.

buildable :: HasBuildInfo a => Bool -> a Source

Is this component buildable?

includes :: HasBuildInfo a => [NonEmptyString] -> a Source

Header files to be included in any compilations via C. Applies to both header files that are already installed on the system and to those coming with the package to be installed.

installIncludes :: HasBuildInfo a => [NonEmptyString] -> a Source

Header files to be installed into $libdir/includes when the package is installed. These files should be found in relative to the top of the source tree or relative to one of the directories listed in include-dirs.

includeDirs :: HasBuildInfo a => [NonEmptyString] -> a Source

List of diretories to search for header files when dealing with C compilations.

cSources :: HasBuildInfo a => [NonEmptyString] -> a Source

C sources to be compiled and lined with the Haskell files.

extraLibraries :: HasBuildInfo a => [NonEmptyString] -> a Source

Extra libraries to link with.

extraLibDirs :: HasBuildInfo a => [NonEmptyString] -> a Source

Directories to search for libraries.

ccOptions :: HasBuildInfo a => [NonEmptyString] -> a Source

C Compiler options.

cppOptions :: HasBuildInfo a => [NonEmptyString] -> a Source

C Preprocessor options. Undocumented, see

ldOptions :: HasBuildInfo a => [NonEmptyString] -> a Source

Linker options.

pkgConfigDepends :: HasBuildInfo a => [Package] -> a Source

List of pkg-config packages needed to build this component.

frameworks :: HasBuildInfo a => [NonEmptyString] -> a Source

OS X frameworks.


Benchmarks, test suites, and executables have common fields; the BuildsExe class captures these.

class BuildsExe a where Source

Sections that build executables. These are the Executable, Benchmark, and TestSuite sections.


mainIs :: NonEmptyString -> a Source

Overloaded function allowing you to use mainIs for an Executable, Benchmark, or TestSuite section.

Exitcode executables

Test suites and benchmarks are capable of building things of type exitcode-stdio-1.0; the BuildsExitcode class reflects this.

class BuildsExitcode a where Source

Sections that build executables that can be exitcode-stdio-1.0. These are the Benchmark and TestSuite sections.


exitcodeStdio :: a Source

Returns a field that is exitcode-stdio-1.0

exitcodeFields Source


:: (BuildsExitcode a, BuildsExe a) 
=> NonEmptyString

Value for main-is field

-> [a] 

Builds two fields. The first indicates that this is an exitcode-stdio-1.0 executable; the second is the appropriate main-is field.


data Betsy m a Source

Computations that can create and use Cabal flags. Use of this type, along with the defaultMain function ensures that any FlagName you use has been properly set up by using makeFlag. That way, you don't use flags in a flag without actually declaring the flag. When defaultMain creates your Cabal file, it will print the necessary Flag sections.

Betsy is parameterized on a type, m. When this type is a monad, Betsy is also a monad, allowing you to use use the usual monad combinators and do notation. Betsy is also a monad transformer.



data FlagName Source

The name of a flag. Only makeFlag creates flags; it will return a FlagName to you. You can then use that FlagName in a conditional using flag.

data FlagOpts Source

Options for flags, except for the flag's name.




flagDescription :: String

A one-line description of what the flag does; this is optional.

flagDefault :: Bool

Is this flag on or off by default?

flagManual :: Bool

If a flag is manual, Cabal will not change its value. If a flag is not manual, Cabal will change its value automatically to attempt to satisfy the package's dependencies.

makeFlag Source


:: Applicative m 
=> NonEmptyString

Name of flag

-> FlagOpts

Options for the flag

-> Betsy m FlagName

This operation will fail if there is already a flag with the name you gave.

Creates new flags.

currentFlags :: Applicative f => Betsy f [(FlagName, FlagOpts)] Source

Returns a list of all flags made so far.


A library consists of one or more LibraryFields. Typically you will return these fields inside of the Betsy type through the defaultMain function. To build a LibraryField, you will mostly use the bindings in the "Build Information" section of this module. You will also need exposedModules and you might use exposed and condBlock.

data LibraryField Source

A field in the Library section of the Cabal file. A Library section can have multiple fields.

exposed :: Bool -> LibraryField Source

Whether a library is exposed. GHC can hide libraries.

exposedModules :: [NonEmptyString] -> LibraryField Source

A library's exposed modules. modules can help you generate this, without you having to manually list each module and keep the list up to date.


An executable consists of one more more ExecutableFields. You build an executable by passing one or more ExecutableFields to the executable function. To get an ExecutableField, you will mostly use the bindings in the "Build Information" section of this module, as well as mainIs. You might also need condBlock.

executable Source


:: NonEmptyString

The name of the executable that Cabal will build.

-> [ExecutableField]

An executable can contain zero or more ExecutableFields.

-> Section 

Builds a Section for executable files.

Test suites

A test suite consists of one more more TestSuiteFields. You build an test suite by passing one or more TestSuiteFields to the testSuite function. To get a TestSuiteField, you will mostly use the bindings in the "Build Information" section of this module. You might also need the testModule, exitcodeStdio, mainIs, detailed, condBlock, and exitcodeFields bindings.

data TestSuiteField Source

A single field value in a TestSuite section. A single test suite section may contain mulitple fields.

testModule :: NonEmptyString -> TestSuiteField Source

The module exporting the tests symbol. This is required when using Detailed and disallowed when using ExitcodeStdio.

testSuite Source


:: NonEmptyString

The executable name for the resulting test suite

-> [TestSuiteField]

Zero or more TestSuiteFields.

-> Section 

Builds a Section for test suites.


A benchmark consists of one more more BenchmarkFields. You build an benchmark by passing one or more BenchmarkFields to the benchmark function. To get an BenchmarkField, you will mostly use the bindings in the "Build Information" section of this module. You might also need the exitcodeStdio, exitcodeFields, and condBlock bindings.

data BenchmarkField Source

A single field in a Benchmark section.

benchmark Source


:: NonEmptyString

The name of the executable file that will be the benchmark

-> [BenchmarkField]

Zero or more benchmark fields.

-> Section 

Builds a Section for benchmarks.

Getting module lists

fileExtensions :: [String] Source

Common extensions of Haskell files and files that are preprocessed into Haskell files. Includes:

  • hs (Haskell)
  • lhs (literate Haskell)
  • gc (greencard)
  • chs (c2hs)
  • hsc (hsc2hs)
  • y and ly (happy)
  • x (alex)
  • cpphs

modulesWithExtensions Source


:: MonadIO m 
=> [NonEmptyString]

Look for files that have these extensions. fileExtensions covers the most common cases. Files without one of these extensions are ignored. Files and directories that do not begin with an uppercase letter are ignored. (This also ignores files that start with a dot.) Directories with a dot anywhere in the name are ignored.

Do not include the leading dot with the extension. For example, to look for Haskell and literate Haskell files only, use

["hs", "lhs"]
-> FilePath

Start searching within this directory.

-> Betsy m [NonEmptyString]

A list of Haskell modules in the given directory tree. The file contents are not examined; only the file names matter. Returned as a list of dotted names.

Gets all Haskell modules in a given directory tree. Allows you to specify what extensions you are interested in. For this to work best, you will want to keep all your library modules in their own directory, such as lib/. You can also separate executables and test suites this way. hsSourceDirs will then tell Cabal to use these directories.


Build types


Cabal file

Usually you will not need this type, as defaultMain produces a Cabal value for you, but you will need it if you want to use bestyToCabalString.

data Cabal Source

Represents an entire Cabal file.

cabal :: Properties -> [LibraryField] -> [Section] -> Cabal Source

Creates a Cabal value when applied to package properties, any applicable library fields, and any sections.

Generating Cabal files

defaultMain - usually all you need

defaultMain :: Betsy IO (Properties, [LibraryField], [Section]) -> IO () Source

Generates a Cabal file. If you have no library, just leave the list of LibraryField empty. Include any and all executables, test suites, benchmarks, and repositories in the list of Section. Ensures that the generated Cabal file also includes any flags you made with makeFlag. If there is an error (such as a duplicate flag) an error message is printed to standard error and the program invokes exitFailure; otherwise, the generated Cabal file is printed to standard output and the program invokes exitSuccess.

Other bindings - for more unusual uses

data Error Source

Errors that may result from running a Betsy computation.


DuplicateFlag FlagName

The user requested creation of a duplicate flag.

Failed String

fail was invoked.


The user requested creation of a flag with an empty name.

class RenderableIndented a where Source

Render an item. The rendered text must contain a newline at the end of each line and must end with a newline. The leftmost line of the rendered text shall be indented by the given number of indentation levels (the number of spaces in each level is set by indentAmt).

If there are no lines to indent, return an empty string.


renderIndented :: Int -> a -> String Source

renderBetsy Source


:: Functor m 
=> Betsy m Cabal 
-> m (Either Error String)

Returns either an error message or the generated Cabal text.

Pure function to obtain Cabal text.

betsyToCabalStringIO :: (MonadIO m, Functor m) => Betsy m Cabal -> m (Either Error String) Source

Like renderBetsy but also uses IO to prepend a header that has the name of the program that is running this function, and the current date and time.