Search code examples
haskelllinkerdependency-managementcabal

How to avoid listing A as build dependency for internal library/executable E just because E depends on internal library L which depends on A?


I have a directory structure like this

.
├── Main.hs
├── mynot.cabal
├── Notification.hs
└── Server.hs

where mynot.cabal looks like this

cabal-version:   3.0
name:            mynot
version:         0.1.0.0

common common
    default-language: GHC2021
    build-depends: base

executable mynot
    import: common
    main-is: Main.hs
    build-depends: dbus
                 , async
                 , containers
                 , extra
                 , flow
    other-modules: Server
                 , Notification

library server
    import: common
    exposed-modules: Server
    build-depends: async
                 , containers
                 , dbus
                 , extra
                 , flow
    other-modules: Notification

library notification
    import: common
    exposed-modules: Notification
    build-depends: containers
                 , dbus
                 , extra

but extra is really only needed by Notification.hs, and I have no clue why I need to list it also under the other stanzas for cabal build to succeed.

I found this, and gave it a try, e.g via

$ mkdir exe
$ mv Main.hs exe/.

and then adding hs-source-dirs: exe in the executable stanza, but the build fails like this

Resolving dependencies...
Build profile: -w ghc-9.4.8 -O1
In order, the following will be built (use -v for more details):
 - mynot-0.1.0.0 (exe:mynot) (configuration changed)
 - mynot-0.1.0.0 (lib:notification) (configuration changed)
 - mynot-0.1.0.0 (lib:server) (configuration changed)
Configuring executable 'mynot' for mynot-0.1.0.0..
Configuring library 'notification' for mynot-0.1.0.0..
Configuring library 'server' for mynot-0.1.0.0..
Preprocessing library 'notification' for mynot-0.1.0.0..
Building library 'notification' for mynot-0.1.0.0..
Preprocessing executable 'mynot' for mynot-0.1.0.0..
Error: cabal-3.10.2.1: can't find source for Server in exe,
/home/enrico/mynot/dist-newstyle/build/x86_64-linux/ghc-9.4.8/mynot-0.1.0.0/x/mynot/build/mynot/autogen,
/home/enrico/mynot/dist-newstyle/build/x86_64-linux/ghc-9.4.8/mynot-0.1.0.0/x/mynot/build/global-autogen

Preprocessing library 'server' for mynot-0.1.0.0..
Building library 'server' for mynot-0.1.0.0..
Error: cabal: Failed to build exe:mynot from mynot-0.1.0.0.

Is something wrong with my project or just something missing in it?


Solution

  • When you use a different hs-source-dirs then that will also influence where cabal will look for the other-modules.

    Instead of using other-modules like that, you should add the components to each others build-depends like this:

    executable mynot
        import: common
        main-is: Main.hs
        build-depends: dbus
                     , async
                     , containers
                     , flow
                     , mynot:server
                     , mynot:notification
        hs-source-dirs: exe
    
    library server
        import: common
        exposed-modules: Server
        build-depends: async
                     , containers
                     , dbus
                     , flow
                     , mynot:notification
        hs-source-dirs: server
    
    library notification
        import: common
        exposed-modules: Notification
        build-depends: containers
                     , dbus
                     , extra
        hs-source-dirs: notification
    

    And you do apparently need to create directories for each component, so the file structure should look like this:

    .
    ├── exe/Main.hs
    ├── mynot.cabal
    ├── notification/Notification.hs
    └── server/Server.hs
    

    Of course you can use different names for the directories if you change the hs-source-dirs fields too.

    Generally, you want each module to be listed in only a single component. Otherwise each component will rebuild that module separately.