Compiling asclepias packages

asclepias project organization

The asclepias repository is organized using a multiple project setup. Some of the subdirectories of the repository such as hasklepias-core, hasklepias-main, etc. contain a Cabal package which we sometimes more generically call a "project" or "sub-project".

Note that in the following sections we often refer to a Haskell module, which for the time-being can be thought of as the contents of a Haskell file (but see the [The Haskell module system] section for a more complete definition).

Cabal packaging overview

Cabal package definition

A Cabal package is defined by the following (see Package Description in the Cabal documentation for full detail).

  • A collection of Haskell files.

  • A package description file with a name of the form package-name.cabal in the package root directory containing metadata about the package.

  • In some circumstances, a file named Setup.hs in the package root directory containing instructions for various setup tasks. This file is only needed when the build-type field in the package description file is specified as Custom (see The Package Field: build-type in the Cabal documentation for more details).

You can locate the various Cabal packages in the repository by using a command such as one of the ones shown below. For this particular project there is also a file hie.yaml (and which is described further in the [A note on using HLS in multi-project repositories] section) that should (unless it gets out-of-sync) accurately describe the package layout.

# Using `find`
find . -path ./dist-newstyle -prune -o -name '*.cabal'

# Alternatively using `fd`
fd --exclude dist-newstyle '\.cabal$'

Cabal package description file format

The Cabal package description file (i.e. the .cabal file) contains information about the package such as the package name, version, structure, and dependencies. Documentation for this file’s format can be found in Package descriptions in the Cabal documentation.

The description file contains a number of top-level fields called Package Properties and which contain information such as cabal-version, name, version, etc. In addition to these fields there may be an arbitrary number of sections from a fixed number of types that are called Component Types. The section type keywords include library, executable, and test-suite among others. Typically each section is named (with one important exception mentioned below) and contains a number of section-specific field/value pairs describing the given component. The fields within a section may optionally be indented, but each field within the section must have the same indentation.

Currently, there can only be one publicly exposed library in a package, and its name is the same as package name set by global name field. The name argument to the library section must be omitted (c.f. Library in the Cabal documentation).

Some fields expect lists for their specified values. For a given field, the thesevalues rather confusingly take exactly one of three forms: space separated (no commas allowed between elements), comma separated (a comma is required between elements), or optional comma separated (the elements may all either be comma seperated or all not comma separated). All optional comma separated fields must follow the same comma or non-comma style (the non-comma style is recommended). The Field Syntax Reference in the Cabal documentation lists the grammar for some of the fields.

Cabal package description file example

Consider the following abridged version of the hasklepias-main.cabal file. In this example we see the top-level fields (i.e. the package properties) cabal-version, name, and version, and following these fields we see a total of three sections. The first section has a "library" component type, and because its name is omitted (i.e. there is no text to the right of the library keyword) this is taken to be the sole publicly exposed library for the package, and is given the same name as is specified by the name field (i.e. hasklepias-main). The second section has a "test-suite" component type, and is given the name examples. The third section has an "executable" component type, and is given the name exampleApp.

The visibility of a given module in a package is controlled by the other-modules, exposed-modules and main-is fields. Note that every package module must be listed in one of these fields. The meanings of these fields are described below.

  • exposed-modules: a list of modules exposed to users of the package (note that this field is applicable only to libraries). Here "exposed" means that package users are able access the functions and data exported by a given exposed module.

  • other-modules:: a list of modules used by the component but not exposed to users.

  • main-is: the name of the file containing the Main module (this field is applicable only to executables).

The meanings of some of the other section-specific fields shown in the example are listed below.

  • default-language: which definition of the Haskell language to use.

  • hs-source-dirs: a list of the directories in which to search for Haskell modules.

  • build-depends: a list declaring the library dependencies required to build the package component.

  • type: has different meanings for various component types. For the test-suite type having a value of exitcode-stdio-1.0 means that the testing interface is an executable where test failure with a non-zero exit code when run.

cabal-version:  2.2
name:           hasklepias-main
version:        0.24.0
description:    Please see the README on GitHub at <https://github.com/novisci/asclepias#readme>
homepage:       https://github.com/novisci/asclepias/#readme
bug-reports:    https://github.com/novisci/asclepias/issues
author:         Bradley Saul
maintainer:     bsaul@novisci.com
copyright:      NoviSci, Inc
category:       Data Science
synopsis:       embedded DSL for defining epidemiologic cohorts
license:        BSD-3-Clause
license-file:   LICENSE
build-type:     Simple
extra-source-files:
    README.md
    ChangeLog.md

library
  exposed-modules:
      Hasklepias
      Hasklepias.Misc
      Hasklepias.Reexports
      Hasklepias.ReexportsUnsafe
  hs-source-dirs:
      src
  build-depends:
    -- Internal packages
      event-data-theory
    , hasklepias-appBuilder
    , hasklepias-core
    , hasklepias-templates
    , stype
    -- External packages
    , aeson == 1.5.6.0
    , base >=4.14 && <5
    , containers == 0.6.5.1
    , flow >= 2.0.0.0
    , generic-lens >= 2.2.0.0
    , microlens >= 0.4.12.0
    , safe >= 0.3
    , tasty  >= 1.4.1
    , tasty-hunit >= 0.10
    , text == 1.2.4.1
    , time >= 1.11
    , tuple >= 0.3
    , witherable >= 0.4.1
    , witch >= 1.0
  default-language: Haskell2010

Compiling asclepias packages

The cabal build command is used to compile Cabal packages and package components. There are many command-line arguments that can be provided with cabal build, however for the sake of brevity these are not covered here. See cabal build --help and cabal-build in the Cabal documentation for full details.

'cabal build' troubleshooting: LLVM version on an M1 Mac

Building on an M1 Mac

The first time you run cabal build on an M1 Mac, you may get errors like:

<no location info>: error:
    Warning: Couldn't figure out LLVM version!
             Make sure you have installed LLVM between [9 and 13)

LLVM is provided as part of Xcode which ships with macOS, but as of January 2022 team members using M1 Macs have reported problems with compiling Haskell packages using the default version of LLVM. If you get the above error message, then you probably need to install a different build of LLVM, which you can do using e.g. homebrew as shown below. Note that in this example we specify LLVM version 12 as suggested in the thread shown below, however the LLVM versions that are compatible with Haskell compilations may change over time. See Help wanted for LLVM config for Haskell on Mac on Reddit for a more detailed discussion.

brew install llvm@12

You will also want to add a line like the following to the appropriate shell startup file such as ${HOME}/.bashrc, ${HOME}/.zshrc, etc.

export PATH="/opt/homebrew/opt/llvm@12/bin:$PATH"

Compiling all asclepias packages

As previously mentioned, the asclepias repository is organized using a multiple project setup. Some of the subdirectories of the repository such as hasklepias-core, hasklepias-main, etc. contain a Cabal package. The simplest thing to do to get started is to build (i.e. compile) all of the Cabal projects in the repository using the following command.

cabal update
cabal build all
This could take around half-an-hour to complete the first time that you do it (future compilations take significantly less time since GHC will only recompile modules that have changed since the last compilation).

By default, Cabal doesn’t compile the test suite or benchmarking modules when using cabal build. In the following sections we will see ways to compile these components if desired.

Compiling asclepias packages one-at-a-time

Alternatively, you can build the packages one-at-a-time using a command of the following form. This is useful when you are working on a particular package and don’t want to compile everything at once in order to save time.

cabal update
cabal build hasklepias-main
Compiling a package will still cause you to compile all of its dependencies. Even if you limit yourself to a single package, it can still take quite a long time the first time that you do it.

By default Cabal doesn’t compile the test suite or benchmarking modules for a given package so if you want to compile the tests along with the package itself then you can use e.g. the --enable-tests and/or --enable-profiling options.

cabal update
cabal build hasklepias-main --enable-tests --enable-profiling

Compiling asclepias package components

In addition to specifying a package name to compile, the cabal build command allows you to specify finer-grained units of compilation called package components, where the package components correspond to the sections in the Cabal package description file. For example, in the example Cabal package description file example section the package name was hasklepias-main, and the package components were called hasklepias-main (a library), examples (a test-suite), and exampleApp an executable (recall that the hasklepias-main library was implicitly named after the package name).

Typically package components are identified using the form package:component (the available syntax is actually more flexible than the form shown here). So for example, you could use the command hasklepias-main:examples to compile the examples component from the hasklepias-main package.

Additionally you can use one of the forms package:ctype or all:ctype to compile all components of the specified type (i.e. the ctype) for a given package or across all packages, respectively. So for example, you could use the command hasklepias-main:executables to compile any components with an executable component type from the hasklepias-main package (of which there happens to be one component, i.e. the exampleApp component), or the command all:executables to compile any components with an executable component type from any package the asclepias repository.

There are other ways of specifying a component by specifying either a module name or the filepath of a module that belongs to the target component, however we do not cover those approaches here.

cabal update

# Using the `package:component` form
cabal build hasklepias-main:hasklepias-main
cabal build hasklepias-main:examples
cabal build hasklepias-main:exampleApp

# Using the `package:ctype` form
cabal build hasklepias-main:libraries
cabal build hasklepias-main:tests
cabal build hasklepias-main:executables

# Using the `all:ctype` form
cabal build all:libraries
cabal build all:tests
cabal build all:executables