Safe Haskell | None |
---|---|
Language | Haskell2010 |
Synopsis
- buildProgramsCustomField :: String
- defaultMain :: IO ()
- buildProgramsUserHooks :: UserHooks
- localBuildInfoWithBuildPrograms :: Args -> Verbosity -> [Program] -> PackageDescription -> LocalBuildInfo -> IO (Either String LocalBuildInfo)
- componentBuildPrograms :: Verbosity -> PackageDescription -> LocalBuildInfo -> Args -> IO [(Component, [String])]
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
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
andreplHook
so when using Stack external programs are not checked when usingstack ghci
orstack 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)
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 #
:: Args |
|
-> Verbosity | |
-> [Program] | The |
-> 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.