[Haskell-cafe] Install a script with Cabal?

Rogan Creswick creswick at gmail.com
Tue Jun 5 19:07:42 CEST 2012

On Tue, Jun 5, 2012 at 4:28 AM, Ketil Malde <ketil at malde.org> wrote:
> Rogan Creswick <creswick at gmail.com> writes:
>>> I have a small project that installs a couple of Haskell tools and a
>>> script that uses these. Cabal will of course build and install the
>>> Haskell programs, but how can I get Cabal to install the script as
>>> well? There's a host of UserHooks available¹, but it'd probably be
>>> better to have an example than to try to experiment with the right
>>> configuration.
>> I don't have an example handy, but I think you'll want preInts,
>> instHook, or postInst.  I'd probably go with postInst unless you need
>> data provided by instHook's type that isn't passed to preInst or
>> postInst.
> I found an example¹ using copyHook. However, this seems to be the wrong
> thing, at least I am unable to get cabal to ever call this hook (or
> preCopy, etc).

I'm not sure if/when copy hooks are used during the typical cabal
configure/build/install process; I suspect they are not used.  There
is, however, a `cabal copy` command, which I believe does.  Not that
that helps you right now, but that may explain the behavior you saw.

>> LocalBuildInfo /probably/ has the details you need (eg: installDirTemplates).

This was harder than I thought.

Here's an example (I hacked an example on to the cabal-dev Setup.hs,
so there are extra imports you won't necessarily need -- full Setup.hs
is in the linked gist, the email only contains the relevant exceprts):

Gist:  https://gist.github.com/2876277

main = defaultMainWithHooks $
       simpleUserHooks { hookedPrograms = [cabalInstallProgram]
                       , postInst = postInstCp (postInst simpleUserHooks)

type PostInstHook = Args -> InstallFlags -> PackageDescription ->
LocalBuildInfo -> IO ()

postInstCp :: PostInstHook -> Args -> InstallFlags ->
PackageDescription -> LocalBuildInfo -> IO ()
postInstCp oldHook args iflags pDesc lbi = do
  let -- The filename to copy from:
      inFile :: FilePath
      inFile = "srcFileName.sh"

      -- The filename to copy to:
      outFile :: FilePath
      outFile = "destFileName.sh"

      prefix = fromFlag $ installDistPref iflags

      -- Make a concrete binDir from the LocalBuildInfo & PackageDescription:
      instBinDir :: FilePath
      instBinDir = bindir $ absoluteInstallDirs pDesc lbi
                                           (fromFlag $ copyDest

      -- layer of indirection, in case we wanted to get a specific
      -- src directory from the cabal file:
      src :: FilePath
      src = inFile

      -- qualify the destination.
      dest :: FilePath
      dest = instBinDir </> outFile

  -- Do the copy, creating outFile in the bin dir:
  copyFile src dest

  -- now invoke the old hook:
  oldHook args iflags pDesc lbi


> I just printed out the contents of LocalBuildInfo, but that's 33 pages
> (I counted) of output.  Redirected to a file, it makes it easier to
> search to find the relevant needles in this haystack.
> Okay, looking at installDirTemplates, I see `bindir` can extract this
> data member.  It's of course an InstallDirTemplate, not a FilePath, but
> I found 'fromPathTemplate', which has the right type.  Except it only
> dumps the template, with $prefix and all.
> Hoogle doesn't seem to know about any of the Cabal stuff, Hayoo has a *very*
> annoying behavior where it cleans out everything you did if you use the
> "back" button, and the "source" links it advertises seem to point into
> the wide blue 404.  But using the latter, I managed to find a link to
> 'substPathTemplate', worked out that the PackageIdentifier it needs is a
> member of PackageDescription, and tried to use that.  Except it gives
> the same result, and doesn't appear to substitute anything.  I guess the
> fact that it is undocumented is a hint that it's the wrong thing to use.
> Reading some more, maybe 'absoluteInstallDirs' is what I'm looking for?
> In addition to the usual heap of huge config structs, it needs a
> "CopyDest", though.  Since I have no idea what to use, I just put
> NoCopyDest there.  Does that make sense?  Okay, it works now, in the
> sense that I ran it once on my laptop, and it copied the file to
> ~/.cabal/bin.  Here is the code, comments welcome:
>  #!/usr/bin/env runhaskell
>  import Distribution.Simple (defaultMainWithHooks, simpleUserHooks,UserHooks(..))
>  import Distribution.Simple.Setup (InstallFlags,CopyDest(..))
>  import Distribution.Simple.Utils (rawSystemExit)
>  import Distribution.PackageDescription (PackageDescription())
>  import Distribution.Simple.LocalBuildInfo (LocalBuildInfo(..), InstallDirs(..), absoluteInstallDirs)
>  import Distribution.Verbosity (normal)
>  main :: IO ()
>  main = defaultMainWithHooks simpleUserHooks
>      { instHook = \pd lbi uh ifs -> myinst pd lbi uh ifs >> instHook simpleUserHooks pd lbi uh ifs }
>  myinst :: PackageDescription -> LocalBuildInfo -> UserHooks -> InstallFlags -> IO ()
>  myinst pd lbi _uh _fs = do
>      let bin = bindir $ absoluteInstallDirs pd lbi NoCopyDest
>      rawSystemExit normal "cp" ["asmeval", bin]
> PS: This was a rather frustrating excercise, and after wallowing through
> a wilderness of modules, types, records and functions, I can't help but
> feel that Cabal is a bit overengineered. Surely including a script or
> similar in a package isn't all that outlandish? Could there conceivably
> have been a simpler way?  Or at least, better documented?  I suspect the
> fact that - with the sole exception of the link below -- I could find *no*
> examples to help me out indicates that it is a bit too complicated.
> -k
> ¹ http://blog.ezyang.com/2010/06/setting-up-cabal-the-ffi-and-c2hs/
> --
> If I haven't seen further, it is by standing in the footprints of giants

More information about the Haskell-Cafe mailing list