61

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
dave4420
  • 46,404
  • 6
  • 118
  • 152
  • Your cabal file is structured in an unfoavourable way, leading to a lot of re-compilation and also introducing the dependency duplication you mention. You can make your tests/executables depend on your library. See http://stackoverflow.com/questions/12305970/how-to-make-a-haskell-cabal-project-with-libraryexecutables-that-still-run-with/12305972#12305972. Starting from there, you can move as much into the direction @Toxaris pointed out in his answer, if you like that. – nh2 Aug 21 '13 at 23:41
  • 1
    There are [plans](https://github.com/haskell/cabal/issues/2832) to add a `common` field to the .cabal format where e.g. shared `build-depends` can be specified. – sjakobi Oct 09 '16 at 05:31

5 Answers5

34

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.

Artem Pelenitsyn
  • 2,508
  • 22
  • 38
Toxaris
  • 7,156
  • 1
  • 21
  • 37
  • 4
    In Cabal-1.26 (as yet unreleased), it will be possibly to replace the `library` stanza with `library my-internal-lib`, which makes an internal library called "my-internal-lib" which is only internally visible to components in your package, and not other users. See https://github.com/haskell/cabal/pull/3022 – Edward Z. Yang Mar 31 '16 at 21:18
  • OTOH, I think exposing internals is okay, so long as it has a suitable name like `Internal` or `Base`. – jpaugh Oct 23 '16 at 03:35
  • You know, i tried this approach, and it was failing when using the same directory for libraries and executables. So i think that having a separated directory is somehow necessary for this to work. The failure led to a message telling me to add dependencies to the `build-depends` section of the library, even though they were there – danza Jan 27 '17 at 11:37
  • Yes, as written in the answer, you have to use different source directories for the library and for executables. – Toxaris Jan 27 '17 at 21:32
  • In the example cabal file you mention, the test-suite section still lists "containers", "mtl", etc while the library section already lists them, too. Isn't this exactly what OP tries to omit? I am asking this because I run into a similar trouble: Importing `Data.Map` in a test suite file does not compile without listing `containers` in the test suite section even though `containers` is already listed in the library section. The error msg is "Failed to load interface for ‘Data.Map.Strict’" – Rolf Apr 26 '17 at 12:19
  • You have to list the packages that the tests themselves depend on. – Toxaris May 04 '17 at 20:56
14

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
phadej
  • 11,947
  • 41
  • 78
5

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.

sjakobi
  • 3,546
  • 1
  • 25
  • 43
-1

No easy way:

  • you can use m4 and specify your dependencies once, but then you will need to reprocess your Cabal file through m4 whenever you change it.

  • you can move the code you are testing out to a library, and then specify the library in your Build-depends for the test. That requires you to install a library even just to run the test.

  • You can just not put the test in the cabal file at all. Build it with ghc --make, which will pull in dependencies. But then you lose cabal integration.

massysett
  • 1,100
  • 6
  • 13
-5

There is an optional library section for .cabal files, which solves your problem.

name:              myprogram
version:           0.1
-- blah blah blah
cabal-version:     >=1.9.2

library
    build-depends: attoparsec == 0.10.*
                 , base == 4.3.*
                 -- long long list of packages

executable myprogram
    hs-source-dirs: src
    main-is:        Main.hs

test-suite test
    hs-source-dirs: test, src
    type:           exitcode-stdio-1.0
    main-is:        Main.hs
    build-depends:  QuickCheck == 2.4.*
indygemma
  • 39
  • 2
  • 2
    This isn't working for me. When I build I get an error message that starts ``Implicit import declaration: Could not find module `Prelude': It is a member of the hidden package `base'.`` – dave4420 Apr 15 '12 at 17:31
  • You still need to depend on base and anything that the `Main.hs` uses – alternative Apr 15 '12 at 17:33
  • 3
    This answer is totally not working for me. Using Cabal version 1.10 I'm just having to write everything in *triplicate*. – Norman Ramsey May 19 '12 at 18:00