cabal-build-programs-0.1.0.1: Adds executable dependencies to the Cabal build

Safe HaskellNone
LanguageHaskell2010

Distribution.Simple.BuildPrograms

Contents

Synopsis

Introduction

This is a Cabal library that lets you annotate your .cabal (or package.yaml) file with external programs that must be available at build time. It does this using a custom Cabal field, x-build-programs which takes a list of executables that are checked whenever the project is built or when starting GHCi (unfortunately the latter does not work with Stack, see "Stack & HPack Gotchas" below). It can also be specified per build component (library, executables, benchmarks, test suite etc.) so that, for instance, a library does not have to depend on an external program used just for benchmarking (eg. gprof).

Why?

This library exists because currently there is no field in Cabal that checks for the existence of non-Haskell programs (eg. '7z.exe' or cmake) at build time. The closest , 'build-tool-depends' only allows Haskell-buildable exectuables like c2hs.

Getting Started

First off you will need to set up a custom build in your .cabal:

build-type:     Custom
custom-setup
  setup-depends:
      Cabal >=2.2.0.0 && <4
    , base >=4.7 && <5
    , cabal-build-programs

or package.yaml file:

build-type:          Custom
custom-setup:
  dependencies:
  - base >= 4.7 && < 5
  - Cabal >= 2.2.0.0 && < 4
  - cabal-build-programs

Then add the external program dependencies, as in this example package.yaml:

verbatim:
  x-build-programs: "cmake"

library:
  ...
  verbatim:
    x-build-programs: "llmv-config"
  when:
    - condition: os(windows)
      then:
        verbatim:
          x-build-programs: "7z.exe"
      else:
        verbatim:
          x-build-programs: "zip"

benchmarks:
  my-benchmarks:
    ...
    verbatim:
      x-build-programs: "gprof,valgrind"

A project that uses the snippet above always depends on cmake no matter which artifact is being built but only the library needs llvm-config. On Windows the library needs 7z.exe for extracting archives, otherwise zip and the benchmarks always need the GNU profiling gprof tool and valgrind.

Now we need a custom Setup.hs that can use this library to check those dependencies.

The easiest way is to use the provided defaultMain. For more advanced use check the docs for buildProgramsUserHooks, overlayUserHooks and localBuildInfoWithBuildPrograms.

Limitations

The biggest limitations of this library is it cannot do any kind of version checking of external programs. For example, it is currently not possible to depend on eg. cmake > 3.0.0. It simply looks around in the environment for the first executable that matches the name.

The Program datastructure provided by Cabal is a lot richer providing a way of extracting versions and doing some post configuration, see the builtin tar as an example. But it is also more complicated use.

Stack & HPack Gotchas

Stack and Hpack have are a few gotchas to keep in mind and since Stack uses HPack it inherits all the issues as well.

  • Stack seems to completely ignore the preRepl and replHook so when using Stack external programs are not checked when using stack ghci or stack repl, stack build seems to work fine.
  • HPack requires that all custom fields be in a "verbatim:" block so the first snippet below is rejected but the second is not:
name:                my-awesome-program
...
x-build-programs:    "cmake,gprof,someOtherDep"
...
name:                my-awesome-program
...
verbatim:
  x-build-programs:  "cmake,gprof,someOtherDep"
...
  • Unlike Cabal, HPack will not accumulate across duplicate custom fields and picks the last one it encounters but only at the same level. For example in the first snippet (a) will be ignored by HPack and you will only see (b) in your .cabal file. But in the second snippet since (a) is inside a conditional it does make it in. To be fair HPack will warn in the first case but it's pretty easy to miss.
executables:
  my-awesome-executable
    main:                Main.hs
    verbatim:
      x-build-programs: "cmake,prof"   (a)
    ghc-options:
    - ...
    verbatim:
      x-build-programs: "someOtherDep" (b)
executables:
  my-awesome-executable
    main:                Main.hs
    when:
    - condition: os(linux)
      verbatim:
        x-build-programs: "cmake,prof" (a)
    ghc-options:
    - ...
    verbatim:
      x-build-programs: "someOtherDep" (b)

buildProgramsCustomField :: String Source #

The custom field to use in Cabal stanza to specify external programs. In a ~.cabal~ file use:

x-build-programs: p1,p2,p3

whereas in the ~package.yaml~ of a Stack or hpack project you must use (note that "p1,p2,p3" are quoted):

verbatim:
  x-build-programs: "p1,p2,p3"

defaultMain :: IO () Source #

This function is the easiest way to use this library in your Setup.hs.

The default Setup.hs:

import Distribution.Simple
main = defaultMain

becomes:

import Distribution.Simple.BuildPrograms -- <- only change
main = defaultMain

buildProgramsUserHooks :: UserHooks Source #

Directly access the UserHooks that back defaultMain when you need finer grained control, they are backed by simpleUserHooks:

import Distribution.Simple.BuildPrograms
import Distribution.Simple(defaultMainWithHooks)
main = defaultMainWithHooks buildProgramUserHooks

localBuildInfoWithBuildPrograms Source #

Arguments

:: Args

buildHook ( extracted from BuildFlags) or replHook arguments

-> Verbosity 
-> [Program]

The hookedPrograms from UserHooks, used to make sure we don't double check built-in build programs like c2hs or ghc

-> PackageDescription 
-> LocalBuildInfo 
-> IO (Either String LocalBuildInfo)

Local build info with the external build programs configured or an error message

This is the lowest level function provided by this library for use in a highly customized Setup.hs scripts. Most of the other functions wrap it in some way.

LocalBuildInfo is a large datastructure that holds all the information required to build a project, this function gathers up only the components that need to be built ( or loaded in GHCi ), extracts the programs in the "x-build-programs" fields, checks that they exist and adds them as ConfiguredProgram to the ProgramDb of the provided LocalBuildInfo and returns a new LocalBuildInfo with the new ProgramDb.

If any of the programs are cannot be found a formatted error message is returned.

The expectation is that it is called from a buildHook or a replHook at some point before the components are built.

componentBuildPrograms :: Verbosity -> PackageDescription -> LocalBuildInfo -> Args -> IO [(Component, [String])] Source #

Used internally it figures out which components to build, extracts the required external programs into a lookup table. To make debugging easier I made it public so you print the table.

overlayUserHooks :: UserHooks -> UserHooks Source #

If your Setup.hs uses non-default or custom UserHooks ,eg. autoconfUserHooks this function will augment the buildHook and replHook to first check for the external programs.

import Distribution.Simple.BuildPrograms
import Distribution.Simple(defaultMainWithHooks,autoconfUserHooks)
main = defaultMainWithHooks (overlayUserHooks autoconfUserHooks)