Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to reduce duplication in the build-depends fields of a .cabal file?

Tags:

haskell

cabal

Here's a .cabal file:

Name:                myprogram
Version:             0.1
-- blah blah blah
Cabal-version:       >=1.9.2

Executable myprogram
  HS-source-dirs:       src
  Main-is:              Main.hs
  Build-depends:        attoparsec == 0.10.*,
                        base == 4.3.*,
                        -- long long list of packages

Test-Suite test
  HS-source-dirs:       test, src
  Type:                 exitcode-stdio-1.0
  Main-is:              Main.hs
  Build-depends:        attoparsec == 0.10.*,
                        base == 4.3.*,
                        -- long long list of packages
                        QuickCheck == 2.4.*

Is there any way I can replace the long list of build-depends packages for the test suite with "same as for the executable, plus QuickCheck"?

Edit: version information.

  • cabal-dev 0.9
  • cabal-install 0.10.2
  • Cabal library 1.10.2.0
  • GHC 7.0.4
  • Haskell Platform 2011.4.0.0
like image 797
dave4420 Avatar asked Apr 15 '12 15:04

dave4420


3 Answers

NOTE: superseded by phadej's answer suggesting common stanzas.


Is there any way I can replace the long list of build-depends packages for the test suite with "same as for the executable, plus QuickCheck"?

Not that I know of. However, there is a way to only mention the list of build-depends packages once, by structuring your project into three targets:

  1. a library that contains all your code, and needs the long build-depends list.
  2. an executable that consists of only one file, and depends on base and the library from above.
  3. a test-suite that depends on the library from above, and the testing packages you are using.

Maybe this approach is what indygemma's answer proposes, but the Cabal file proposed there will not quite achieve it, as Norman Ramsey points out in a comment. Here's the main points of what you need in a Cabal file. For a full example that works for me, you can look at this Cabal file.

name: my-program
version: ...

library
  hs-source-dirs: src-lib
  build-depends: base, containers, ...
  exposed-modules: My.Program.Main, ...

executable my-program
  hs-source-dirs: src-exec
  main-is: my-program.hs
  Build-depends: base, my-program

test-suite tests
  type: exitcode-stdio-1.0
  hs-source-dirs: src-test
  main-is: tests.hs
  other-modules: ...
  build-depends: base, my-program, test-framework, ...

Important points:

  • There are three separate source directories for the three targets. This is necessary to stop GHC from recompiling library files when building the other targets.

  • All of the application code is in the library. The executable is just a wrapper, like this:

    import My.Program.Main (realMain)
    main = realMain
    
  • The library exposes all modules that are necessary for testing.

The last point highlights the drawback of this approach: You end up having to expose internal modules. The main benefit of this approach is that you have less duplication in the Cabal file, and maybe more importantly, less duplication in the build process: The library code will be built only once, and then linked into both the executable and the test-suite.

like image 178
Toxaris Avatar answered Nov 08 '22 03:11

Toxaris


Since version 2.2 Cabal supports common stanzas, to dedup build info fields: https://cabal.readthedocs.io/en/latest/developing-packages.html#common-stanzas

cabal-version:       2.2
name:                myprogram
version:             0.1
-- blah blah blah

common deps
  build-depends: base ^>= 4.11,
                 -- long long list of packages
  ghc-options: -Wall

library
  import: deps
  exposed-modules: Foo

test-suite tests
  import: deps
  type: exitcode-stdio-1.0
  main-is: Tests.hs
  build-depends: foo
like image 12
phadej Avatar answered Nov 08 '22 03:11

phadej


You could also consider using hpack instead of writing the .cabal file by hand:

In hpack's package.yaml format, you can specify a common dependencies field whose entries are added to every components' build-depends field when generating the .cabal file.

For example, see hpack's own package.yaml and the generated hpack.cabal.

To start using hpack with an existing package, you can use hpack-convert which will generate the package.yaml from an existing .cabal file.

To create a new package that uses hpack, you can use stack's simple-hpack template like so: stack new mypkg simple-hpack.

If you use stack for development, you don't have to call hpack manually to regenerate the .cabal file from an updated package.yaml – stack will do that automatically.

like image 5
sjakobi Avatar answered Nov 08 '22 03:11

sjakobi