How to manage XMonad (and xmobar and anything else related to it) via GHCup+cabal?
Asked Answered
D

2

2

It turns out it was enough to execute

cabal install --package-env=$HOME/.config/xmonad --lib base xmonad xmonad-contrib

but what I don't understand is the following:

  • why did this work?
  • Why updating GHC via GHCup "breaks" the modules?
  • When I write "breaks", given the original question below, should I say "hides"?
  • What is a hidden package?
  • Is executing cabal install --lib the-stuff-that-hls-claims-hidden wrong?
  • What is the correct way of updating with GHCup? (As apparently just running ghcup tui and doing i and s on new stuff and u on old stuff leaves things in a bad state.)

I think they all boil down to the some point: my misunderstanding of GHCup and the Haskell ecosystem in general.


Orignal question

When I started using XMonad, I had a hard time just running it, the reason being that I had installed via pacman like any other program, as I'm on ArchLinux, but I also had ghc installed in my home, via ghcup, and available via the PATH. Eventually I asked on ArchLinux forum, and manage to fix the problem.

My choice, at that time, was to install xmonad via cabal (installed via ghcup). The reason for my choice is that I like GHCup and I find ghcup tui a very convenient way to have the most recent recommended version of HLS, the most recent version of Cabal, the most recent recommended and hls-powered version of GHC.

And I thought I had understood what the implications of using XMonad installed via cabal was.

But apparently I'm wrong: I've just updated via GHCup 0.1.20.0 to GHC 9.4.8, Cabal 3.10.2.1, and HLS 2.5.0.0, ...

... and now I can't recompile XMonad, the error being along the lines of "I can't find a single thing!", as you can see:

Errors detected while compiling xmonad config: /home/enlico/.config/xmonad/xmonad.hs
$ ghc --make xmonad.hs -i -ilib -fforce-recomp -main-is main -v0 -outputdir /home/enlico/.cache/xmonad/build-x86_64-linux -o /home/enlico/.cache/xmonad/xmonad-x86_64-linux

xmonad.hs:2:1: error:
    Could not find module ‘XMonad’
    Use -v (or `:set -v` in ghci) to see a list of the files searched for.
  |
2 | import XMonad
  | ^^^^^^^^^^^^^

xmonad.hs:3:1: error:
    Could not find module ‘XMonad.Hooks.DynamicLog’
    Use -v (or `:set -v` in ghci) to see a list of the files searched for.
  |
3 | import XMonad.Hooks.DynamicLog
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

xmonad.hs:4:1: error:
    Could not find module ‘XMonad.Hooks.EwmhDesktops’
    Use -v (or `:set -v` in ghci) to see a list of the files searched for.
  |
4 | import XMonad.Hooks.EwmhDesktops
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

xmonad.hs:5:1: error:
    Could not find module ‘XMonad.Hooks.StatusBar’
    Use -v (or `:set -v` in ghci) to see a list of the files searched for.
  |
5 | import XMonad.Hooks.StatusBar
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

xmonad.hs:6:1: error:
    Could not find module ‘XMonad.Hooks.StatusBar.PP’
    Use -v (or `:set -v` in ghci) to see a list of the files searched for.
  |
6 | import XMonad.Hooks.StatusBar.PP
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

xmonad.hs:7:1: error:
    Could not find module ‘XMonad.Layout.NoBorders’
    Use -v (or `:set -v` in ghci) to see a list of the files searched for.
  |
7 | import XMonad.Layout.NoBorders
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

xmonad.hs:8:1: error:
    Could not find module ‘XMonad.Layout.Spacing’
    Use -v (or `:set -v` in ghci) to see a list of the files searched for.
  |
8 | import XMonad.Layout.Spacing
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

xmonad.hs:9:1: error:
    Could not find module ‘XMonad.Util.EZConfig’
    Use -v (or `:set -v` in ghci) to see a list of the files searched for.
  |
9 | import XMonad.Util.EZConfig (additionalKeysP)
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

xmonad.hs:10:1: error:
    Could not find module ‘XMonad.Util.Loggers’
    Use -v (or `:set -v` in ghci) to see a list of the files searched for.
   |
10 | import XMonad.Util.Loggers
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^

xmonad.hs:11:1: error:
    Could not find module ‘XMonad.Util.Ungrab’
    Use -v (or `:set -v` in ghci) to see a list of the files searched for.
   |
11 | import XMonad.Util.Ungrab
   | ^^^^^^^^^^^^^^^^^^^^^^^^^

xmonad.hs:12:1: error:
    Could not find module ‘XMonad.StackSet’
    Use -v (or `:set -v` in ghci) to see a list of the files searched for.
   |
12 | import qualified XMonad.StackSet as W
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The unsuccessful attempt to fix things was:

cabal install --overwrite-policy=always --package-env=$HOME/.config/xmonad xmonad xmonad-contrib

The error above is upon recompiling XMonad, but beware, I've re-tried rempiling in order to get the error to paste here in the question, so I'm not 100% sure the error was identical upon recompiling XMonad right after I run the cabal command above.


Incidentally, I've got used to the fact that when I update stuff via GHCup than something goes wrong with some modules.

For instance, right now I've opened a haskell file I used for a hackerrank challenge or something, and I notice that one line is marked as erroneous by HLS:

import Flow ((|>))

Executing cabal install --lib flow fixes that.

But upon reloading HLS, another line shows an error:

import qualified Data.Map.Strict as M

and the error, now that I read, is this:

Could not load module ‘Data.Map.Strict’
It is a member of the hidden package ‘containers-0.6.7’.
You can run ‘:set -package containers’ to expose it.
(Note: this unloads all the modules in the current scope.)

and executing cabal install --lib containers fixes is, but was that the right step?

Distefano answered 8/1, 2024 at 19:9 Comment(0)
M
1

Quoting assorted comments:

Also, with "Is executing cabal install --lib the-stuff-that-hls-claims-hidden wrong?" I was not referring to xmonad only, but in general to any package.

Opinions may vary, and I'm open to be corrected on this take, but I understand cabal install --lib as a way to approximate how things used to work in the old days of cabal-install v1 (that is, making things available in a shared environment). With that, however, the old cabal-install v1 problems tend to return (namely, running into version conflicts once you install or update enough stuff). Furthermore, as you have already noticed, cabal install --lib relies on hidden per-directory state (the environment files) that can be easy to forget about.

My current practice, instead, is to give anything its own subdirectory and .cabal file, even quick experiments. That might look like a lot of ceremony, but it really doesn't have to be. Once you remove all the metadata you don't care about for something you won't publish, a .cabal file can be very minimal. Besides keeping your builds from interfering with each other, by having a project for your code you gain confidence in being able to successfully build it at a later point. Declaring dependencies explicitly in the .cabal file is a good first step in that direction; depending on how much you care about reproducibility for some particular piece of code, you can go further and add extra information of various kinds.

I suspect the answer would still be "make you cabal config file". I tried cabal init in xmonad.hs's dir, but I'm unsure how to proceed

This is the .cabal file for my XMonad configuration once I remove all the optional fields:

cabal-version:       3.4
name:                xmonad-duplode
version:             0.1.0.0

executable xmonad
  main-is:             xmonad.hs
  build-depends:       base >= 4.13
                     , xmonad >= 0.16
                     , xmonad-contrib >= 0.16
                     , directory
                     , filepath

In particular, if your xmonad.hs uses flow, add it to the build-depends and it will just work next time you compile your code.

For this kind of code meant for personal use, it's up to you whether to have version bounds on the dependencies, and how tight to make them. You might e.g. find it of some use to add a lower bound once you begin using a feature that's only available from a certain version onward.

(A different approach that also can make sense for some kinds of personal use code is not putting any version bounds, but using cabal freeze to generate a freeze file, which pins exact versions of everything. Then, when you feel like updating GHC or any of the dependencies, delete the freeze file and generate it again once you're done updating.)

Package name? Why should it be not "xmonad"? The question The name xmonad is already in use by another package on Hackage. Do you want to choose a different name (y/n)? makes me think I'm doing something wrong.

In the .cabal file quoted above, the package name is xmonad-duplode, while the executable name is xmonad. Your project will depend on the xmonad package, so avoiding the clash does make sense.

but if my intention was to always update to the most recent "recommended+hls-powered" GHC version (fundamentally because I don't have any Haskell project that could suffer from being too bleeding edge; plus "recommended" doesn't feel bleeding edge anyway, so what am I risking at all..) and to keep xmonad in sync with that, would updating .cabal and run cabal build... after every GHC update fundamentally be the same thing as the approach I'd adopted so far?

If your only project configuration is a minimal .cabal file like the one above, your XMonad will be built with the default GHC set by GHCup. While that may well be the most convenient option for your purposes, it has a few minor downsides -- for instance, if some change in base in a brand new GHC you got from GHCup ever requires you to tweak your xmonad.hs, you'll have to do it before being able to recompile it.

One easy way to pin down the compiler version, so that you don't have to think about it until you want to, is to have, alongside the .cabal file, a cabal.project file, which might look like this:

packages: .

with-compiler: ghc-9.4.8

(In a public project, the with-compiler setting would be better placed in a separate cabal.project.local file that you wouldn't commit to the public repository, but here it probably doesn't matter.)

With the cabal.project file in place, you can manage your GHC versions as usual with GHCup, and your XMonad will keep being built with the specified GHC version regardless of whatever else you're doing. When you feel like trying out a different GHC with your XMonad config, just change the with-compiler field.

The cabal.project file is also useful if you want to try bleeding edge versions of xmonad and xmonad-contrib, as it can be used to pull dependencies from the development repositories rather than Hackage.


Here goes one way of setting up XMonad with a cabal project in ~/.xmonad along the lines of what we've been discussing. Begin by creating your xmonad.hs, the .cabal file and, if you wish so, the cabal.project file in ~/.xmonad. Then, as suggested by Daniel Wagner, from your .xmonad directory run (replacing ~/.local/bin by a destination of your preference in your PATH):

cabal build && cp "$(cabal list-bin xmonad)" ~/.local/bin

The above, which only needs to be done a single time, will place a "bootstrap" xmonad executable (the one that will be called by .xinitrc) in ~/.local/bin. It corresponds to the second cabal install (the one without --lib) in the relevant section of the official installation guide; I prefer doing it this way because for our current purposes there's no need or advantage in involving the global cabal store.

Next, create (and make executable) the build script in .xmonad. The script below is adapted from this comment by Tony Zorman on XMonad GitHub issue #403:

#!/bin/sh
cd "$HOME/.xmonad" && cabal build || exit
ln -sfT "$(cabal list-bin xmonad)" "$1"

With that in place, both the "bootstrap" binary (on startup) and the running binary (when you use the recompile command) will build the executable and symlink the binary thus produced by cabal-install to the destination expected by XMonad (which gets passed as $1).


The setup described just above for XMonad can also be used for xmobar, if you're compiling your configuration by using it as a library. In the xmobar configuration directory (the default is ~/.config/xmobar), put your xmobar.hs, create a .cabal file...

cabal-version:       3.4
name:                xmobar-duplode
version:             0.1.0.0

executable xmobar
  main-is:             xmobar.hs
  build-depends:       base
                     , xmobar

... optionally, a cabal.project file (it can be used to enable optional features of the xmobar library)...

packages: .

-- Leave out if you don't care about the compiler version.
with-compiler: ghc-9.4.8

-- Note the package in this stanza is xmobar, the library dependency,
-- and not xmobar-duplode, the executable with the configuration.
-- See the xmobar Hackage docs for the list of flags.
package xmobar
  flags: +all_extensions

... and a build script...

#!/bin/sh
cd "$HOME/.config/xmobar" && cabal build || exit
ln -sfT "$(cabal list-bin xmobar)" "$1"

... then create the "bootstrap" binary by running cabal install xmobar outside of ~/.config/xmobar, so that cabal will install Hackage xmobar rather than your custom one -- installing the "bootstrap" binary from Hackage is the easier option in the case of xmobar.

Mayle answered 15/1, 2024 at 2:56 Comment(29)
will keep being built with the specified GHC version, I guess I don't think in these terms as I usually nuke all but the most recent GHC I have, ahah. I guess I should refer to When you feel like trying out. Regarding if some change [...] ever requires you to tweak, I'm think of "something happened that will cause a compile-time failure"; if that's what you allude to, then why have to do it before being able to recompile it? If I try recompiling it, don't I just get a compilation error telling me what's wrong, reminding me that I have to "sanitize" my xmonad.hs/xmobar.hs?Distefano
Ok, XMonad works. But my xmobar is gone :( How do I handle this? Can I do something for the xmobar executable to be generated as part of building this project? Or should I make a similar ad-hoc project for it and then have xmonad.hs just spawn the xmobar executable? This latter idea feels a bit wrong, as it would require recompiling 2 things. Oh, when I say xmobar, I'm referring to the approach of using it as a library, i.e. having an actual Haskell xmobar.hs file, rather than an xmobarrc config file.Distefano
Ok, I've definitely screwed everything up :D I also delete ~/.cabal thinking it would be re-generated by re-installing cabal and then doing cabal update. :/Distefano
I've rerun cabal update, cabal install --package-env=$HOME/.config/xmonad --lib base xmonad xmonad-contrib and cabal install --package-env=$HOME/.config/xmonad xmonad, deleting existing files that caused errors (I guess I could have equally used --force-reinstalls and --overwrite-policy=always), but I don't seem to be able to get back to a working XMonad. Currently I get xmonad not in $PATH: ....Distefano
@Distefano Okay, let's begin from the end. Given you're using cabal install, I assume you're following the steps from the XMonad user guide, as opposed to those in this answer. Since you have deleted ~/.cabal and (I presume) are using a very recent cabal, it might be putting things in the XDG standard directories rather than recreating ~/.cabal. That being so, I suggest checking if the xmonad binary produced by the second cabal install is in ~/.local/bin, and if ~/.local/bin is in your path.Mayle
you're following the steps from the XMonad user guide, as opposed to those in this answer, that's just an attempt to get back to what worked a few hours ago. Eventaully I'd like to try use your approach (for which I miss the part explained in my second comment above). But for now I'd also like to understand what the heck I have done. Yes, the second cabal install ends with Symlinking 'xmonad' to '/home/enrico/.local/bin/xmonad'.Distefano
And I have export PATH="$HOME/.cabal/bin/xmonad:$PATH" in my ~/.bashrc. This makes me think that the ~/.cabal directory I had... was probably a remnant of who knows how long ago? I have now cabal 3.10.2.1, but maybe when I installed it the first time, long ago, it used to put things in ~/.cabal?Distefano
@Distefano Yeah -- the switch to XDG directories is fairly recent, I believe, and even the newer versions will keep using ~/.cabal if that directory exists, so that's likely what happened. Anyway, given there indeed is a ~/.local/bin/xmonad, adding ~/.local/bin to your path should get you back to the setup you had previously.Mayle
That's the same I thought, but upon trying to re-build xmonad (via Ctrl+q) I get the error xmonad not in $PATH: ..., with the path being the old one. How do I make xmonad digest my new PATH as defined in the ~/.bashrc?Distefano
@Distefano This sounds like xmonad issue #400: ~/.bashrc is usually not read by XMonad or other windowing systems. I suggest setting adding ~/.local/bin to PATH in ~/.bash_profile instead -- that's what I do here, following the Arch wiki.Mayle
Oh, right, I source .bashrc in .bash_profile, that's why it is available when I start X! Anyway, I'm not at the point that XMonad works, but xmobar.hs is not picked up, because Could not find module ‘Xmobar’ It is not a module in the current program, or in any known package., and if I try cabal install xmonad (with or without --package-env=$HOME/.config/xmonad and/or --lib) I get this error.Distefano
Oh, I had that problem in your error while getting my XMonad setup to work again. It boils down to cabal issue #9608, and has nothing to do with anything we're doing. The workarounds, while we wait for the patches to land, are described in the issue: either downgrade libvpl to openvpl in Arch, or edit /usr/lib/pkgconfig/vpl.pc in your system to remove the non-ASCII (R) characters.Mayle
(The butterfly wings here are openvpl getting repackaged as libvpl in a recent Arch update. libvpl has a buggy pkg-config file which cabal fails to parse. As a consequence, cabal can't install pango, which is configured using pkg-config and is a dependency of current xmobar. cabal then tries to fall back to a very old xmobar version (0.14) that doesn't depend on pango, but that one is so old that it won't build with recent GHCs, leading to the errors you saw.)Mayle
Thanks for that!! Now I'm back to where I was, and hopefully I can try using your approach instead, but without to much risk as I should be able to fix again if I break it again :DDistefano
Ok, so at the moment I've made ~/.config/xmonad a symbolic link to a directory with what I had before screwing everything up, and I can easily re-point it to a directory where I'm trying your approach. The trouble I'm having in the latter case, is the one I explained in my second comment to this answer. Do you know how can I deal with that?Distefano
@Distefano (1) While my usual setup doesn't use xmobar-as-a-library, it turns out you can use pretty much the same setup with a .cabal file alongside xmobar.hs in ~/.config/xmobar. I have updated the answer to cover that. (2) xmobar is a separate executable, with its own XMonad-like recompilation mechanism that will look for its own recompiling script that's also called build. That being so, I don't think keeping the two configurations in the same project will be worth the trouble.Mayle
Oh, this seems to work pretty nicely. I guess my original concern that This latter idea feels a bit wrong, as it would require recompiling 2 things is easily solved, I just need to rebind the M-q key to not just recompile and relaunch XMonad using but also recompile XMobar!Distefano
@Distefano I don't think you actually need to rebind M-q, it should work straight away. Assuming you're spawning xmobar from xmonad.hs, restarting XMonad will also restart xmobar, which, in turn, will lead to xmobar being recompiled.Mayle
XMonad does restart XMobar, as I can see by printing pidof xmobar before and after a restart of XMonad, obtaining a difference PID. However, if I change xmobar.hs, simply kill $(pidof xmobar) && xmobar & shows that the change is not absorbed, unless I do ./build ~/.local/bin/xmobar before restarting. The way I spawn XMobar from XMonad is withEasySB (statusBarProp (unwords ["xmobar", xmobarhs]) (pure myXmobarPP)) toggleStrutsKey, where toggleStrutsKey XConfig { modMask = m } = (m, xK_b) and xmobarhs = "~/.local/xmobar/xmobar.hs".Distefano
I guess you were referring to when running the system-wide xmobar, it will notice that you have your own implementation and (re)compile and run it as needed.Distefano
@Distefano [1/2] You're right, I misinterpreted some of what I saw yesterday. The underlying complication, I think, is that while the xmonad function from XMonad.Main handles recompiling with the build script, its analogue xmobar from Xmobar.App.Main doesn't, and so main = xmobar config isn't enough to make recompiling work.Mayle
@Distefano [2/2] The easiest way out, then, indeed is installing the "bootstrap" xmobar executable from Hackage, as it already covers that (via xmobarMain from Xmobar.App.Main). Remove the xmobar binary from ~/.local/main, and, if you don't already have the Hackage one in ~/.cabal/bin, run cabal install xmobar (no --lib, no --package-env) from anywhere except ~/.config/xmobar (so that cabal won't pick your custom xmobar).Mayle
(I'm assuming you meant Remove the xmobar binary from ~/.local/bin.) Last suggestion doesn't quite cut it yet. Upon doing cabal install xmobar from ~, I see that ls -l ~/.local/bin/xmobar has changed pointee, so I know the build worked. And clearly if I reload XMonad, I see in the bar whatever change I made to xmobar.hs before running the aforementioned cabal command line. However, if I then modify xmobar.hs again and re-reload XMonad, the changes are not shown, meaning that the build script is not rerunning, I guess.Distefano
Incidentally (and this is the first time I try) I'm seeing that starting from a "normal" state, everytime I re-building XMonad twice fast enough... one xmonad process is killed, and 2 are created. So if I keep doing it, I end up spawning a lot of them :|Distefano
I think the key is in the comment: the xmonad executable is older than @xmonad.hs@ or any file in the @lib@ directory (under the configuration directory) in XMonad.Core. I guess when you wrote I misinterpreted you were rolling back the suggestion of having two separate projects, and I should probably just put the xmobar.hs file in a lib subdirectory beside xmonad.hs. I'll give a try.Distefano
Let us continue this discussion in chat.Mayle
But so we run cabal install xmobar to install xmobar from Hackage, which results in /home/enrico/.local/bin/xmobar (which is what which xmobar echoes, given that dir is in the PATH) to point to the ../state/cabal/store/ghc-9.4.8/xmobar-0.47.3-e-xmobar-9fe877c28e834941690ce32ec183246fa150137c8bb0ee520a0b5039f0a0ebd5/bin/xmobar executable which is able to handle the recompilation thing. But then the build script will symlink /home/enrico/.config/xmobar/xmobar to dist-newstyle/build/x86_64-linux/ghc-9.4.8/xmobar-aster-0.1.0.0/x/xmobar/build/xmobar/xmobar. Why/how is that used?Distefano
As in, if we need the Hackage binary because it can deal with recompilation, then why isn't my xmobar config consisting just of xmobar.hs? Why do I have a whole cabal package around it?Distefano
@Distefano The Hackage binary includes the code which detects changes to your xmobar.hs, recompiles it if needed, and launches the resulting executable from ~/.config/xmobar/xmobar -- see Xmobar.App.Main. In principle, you could bring all of that into your code, adapting it so that the Hackage binary is no longer needed, but it's probably easier to just rely on the Hackage binary for initialisation.Mayle
T
2
  • why did this work?
  • Why updating GHC via GHCup "breaks" the modules?

GHC versions its libraries not just by library version, but by compiler version, too: each separate compiler maintains its own separate repository of packages, which do not coordinate with each other in any way. So the short version of what's going wrong is that when ghcup installs a new version of GHC, the only packages available are the ones that come with GHC itself, until you do something to change that.

  • When I write "breaks", given the original question below, should I say "hides"?
  • What is a hidden package?

No, definitely don't say "hides", as that is a technical term already claimed by the GHC package database. Within a package database, packages may be either "exposed" or "hidden"; when you build a new thing directly using GHC (i.e. not via cabal-install) or launch ghci directly (i.e. not via cabal repl), you may only directly depend on exposed packages (though you are allowed to transitively depend on hidden packages).

  • Is executing cabal install --lib the-stuff-that-hls-claims-hidden wrong?

Since you have discovered and are using package environments, I think that's fine. Very slightly better would be to turn your xmonad config into its own cabal package – i.e. with a .cabal file that lists package dependencies and version constraints, etc. – and run cabal build && cp $(cabal list-bin xmonad) <wherever you need the xmonad executable to go>. One notable advantage of this approach is that you can write a project file that specifies a particular GHC version; then future builds will attempt to use that version even if you have installed a newer GHC, reducing build time and increasing build stability. If you want to try this, you can use cabal configure -w ghc-9.2 (or similar) to write the project file for you.

  • What is the correct way of updating with GHCup? (As apparently just running ghcup tui and doing i and s on new stuff and u on old stuff leaves things in a bad state.)

First update GHC, then update GHC's package database with cabal. Uninstalling older versions of GHC isn't necessary unless you are in a disk space crunch, and may even be undesirable if you have written a project file as discussed above.

Teodorateodorico answered 8/1, 2024 at 21:10 Comment(10)
I think that's fine, then why should I not use the same approach with xmonad, as in, reinstalling it everytime I update GHC? Is it just a matter of risk?Distefano
Also, with Is executing cabal install --lib the-stuff-that-hls-claims-hidden wrong? I was not referring to xmonad only, but in general to any package.Distefano
@Distefano In general, you should consider cabal install --lib ... an advance form of installation, only for users who knows what they are doing. The most convinient way is to create a .cabal file which describes which packages you want within that environment.Gothenburg
@Distefano IInstalling a library to a package environment is fine, but not preferred. I think I outline in the answer already some of the reasons that making a cabal package is preferred. These comments apply equally to xmonad and to non-xmonad workflows.Teodorateodorico
@DanielWagner, but if my intention was to always update to the most recent "recommended+hls-powered" GHC version (fundamentally because I don't have any Haskell project that could suffer from being too bleeding edge; plus "recommended" doesn't feel bleeding edge anyway, so what am I risking at all..) and to keep xmonad in sync with that, would updating .cabal and run cabal build... after every GHC update fundamentally be the same thing as the approach I'd adopted so far?Distefano
@Distefano Ideally you would not need to update the .cabal file after every GHC update.Teodorateodorico
@DanielWagner, regarding when you build a new thing directly using GHC (i.e. not via cabal-install) or launch ghci directly (i.e. not via cabal repl), you may only directly depend on exposed packages, I've hit another problem today. In a random Haskell file in my home (resp. in xmonad.hs), I can 'import Flow` but not import XMonad (resp. the other way around), because It is a member of the hidden package ‘flow-2.0.0.4’ (resp. ‘xmonad-0.17.2’).Distefano
(cont.ed) Now, I assume that I can't import XMonad in a file out of that dir because I installed it via cabal install --package-env=$HOME/.config/xmonad --lib (this is probably encoded in .ghc.environment.x86_64-linux-9.4.8 in that dir). But how do I make Flow available in that dir?Distefano
I suspect the answer would still be "make you cabal config file". I tried cabal init in xmonad.hs's dir, but I'm unsure how to proceed: What does the package build:-> library/exec/both? Overwrite existing files? And which ones would be overridden? Version of cabal: I see many listed, but not the one of cabal --version (which is the same shown in ghcup tui) Package name? Why should it be not "xmonad"? The question The name xmonad is already in use by another package on Hackage. Do you want to choose a different name (y/n)? makes me think I'm doing something wrong.Distefano
@Distefano For direct control over GHC package databases, you can use ghc-pkg. You may have to specify which environment file to use, I don't know; it's been ages since I've meddled with this stuff manually rather than relying on cabal. If making a package, you are creating an executable, you probably don't want to overwrite stuff you've already made (?), and yes you should not use the package name xmonad, especially since you will almost certainly be depending on the real xmonad package!Teodorateodorico
M
1

Quoting assorted comments:

Also, with "Is executing cabal install --lib the-stuff-that-hls-claims-hidden wrong?" I was not referring to xmonad only, but in general to any package.

Opinions may vary, and I'm open to be corrected on this take, but I understand cabal install --lib as a way to approximate how things used to work in the old days of cabal-install v1 (that is, making things available in a shared environment). With that, however, the old cabal-install v1 problems tend to return (namely, running into version conflicts once you install or update enough stuff). Furthermore, as you have already noticed, cabal install --lib relies on hidden per-directory state (the environment files) that can be easy to forget about.

My current practice, instead, is to give anything its own subdirectory and .cabal file, even quick experiments. That might look like a lot of ceremony, but it really doesn't have to be. Once you remove all the metadata you don't care about for something you won't publish, a .cabal file can be very minimal. Besides keeping your builds from interfering with each other, by having a project for your code you gain confidence in being able to successfully build it at a later point. Declaring dependencies explicitly in the .cabal file is a good first step in that direction; depending on how much you care about reproducibility for some particular piece of code, you can go further and add extra information of various kinds.

I suspect the answer would still be "make you cabal config file". I tried cabal init in xmonad.hs's dir, but I'm unsure how to proceed

This is the .cabal file for my XMonad configuration once I remove all the optional fields:

cabal-version:       3.4
name:                xmonad-duplode
version:             0.1.0.0

executable xmonad
  main-is:             xmonad.hs
  build-depends:       base >= 4.13
                     , xmonad >= 0.16
                     , xmonad-contrib >= 0.16
                     , directory
                     , filepath

In particular, if your xmonad.hs uses flow, add it to the build-depends and it will just work next time you compile your code.

For this kind of code meant for personal use, it's up to you whether to have version bounds on the dependencies, and how tight to make them. You might e.g. find it of some use to add a lower bound once you begin using a feature that's only available from a certain version onward.

(A different approach that also can make sense for some kinds of personal use code is not putting any version bounds, but using cabal freeze to generate a freeze file, which pins exact versions of everything. Then, when you feel like updating GHC or any of the dependencies, delete the freeze file and generate it again once you're done updating.)

Package name? Why should it be not "xmonad"? The question The name xmonad is already in use by another package on Hackage. Do you want to choose a different name (y/n)? makes me think I'm doing something wrong.

In the .cabal file quoted above, the package name is xmonad-duplode, while the executable name is xmonad. Your project will depend on the xmonad package, so avoiding the clash does make sense.

but if my intention was to always update to the most recent "recommended+hls-powered" GHC version (fundamentally because I don't have any Haskell project that could suffer from being too bleeding edge; plus "recommended" doesn't feel bleeding edge anyway, so what am I risking at all..) and to keep xmonad in sync with that, would updating .cabal and run cabal build... after every GHC update fundamentally be the same thing as the approach I'd adopted so far?

If your only project configuration is a minimal .cabal file like the one above, your XMonad will be built with the default GHC set by GHCup. While that may well be the most convenient option for your purposes, it has a few minor downsides -- for instance, if some change in base in a brand new GHC you got from GHCup ever requires you to tweak your xmonad.hs, you'll have to do it before being able to recompile it.

One easy way to pin down the compiler version, so that you don't have to think about it until you want to, is to have, alongside the .cabal file, a cabal.project file, which might look like this:

packages: .

with-compiler: ghc-9.4.8

(In a public project, the with-compiler setting would be better placed in a separate cabal.project.local file that you wouldn't commit to the public repository, but here it probably doesn't matter.)

With the cabal.project file in place, you can manage your GHC versions as usual with GHCup, and your XMonad will keep being built with the specified GHC version regardless of whatever else you're doing. When you feel like trying out a different GHC with your XMonad config, just change the with-compiler field.

The cabal.project file is also useful if you want to try bleeding edge versions of xmonad and xmonad-contrib, as it can be used to pull dependencies from the development repositories rather than Hackage.


Here goes one way of setting up XMonad with a cabal project in ~/.xmonad along the lines of what we've been discussing. Begin by creating your xmonad.hs, the .cabal file and, if you wish so, the cabal.project file in ~/.xmonad. Then, as suggested by Daniel Wagner, from your .xmonad directory run (replacing ~/.local/bin by a destination of your preference in your PATH):

cabal build && cp "$(cabal list-bin xmonad)" ~/.local/bin

The above, which only needs to be done a single time, will place a "bootstrap" xmonad executable (the one that will be called by .xinitrc) in ~/.local/bin. It corresponds to the second cabal install (the one without --lib) in the relevant section of the official installation guide; I prefer doing it this way because for our current purposes there's no need or advantage in involving the global cabal store.

Next, create (and make executable) the build script in .xmonad. The script below is adapted from this comment by Tony Zorman on XMonad GitHub issue #403:

#!/bin/sh
cd "$HOME/.xmonad" && cabal build || exit
ln -sfT "$(cabal list-bin xmonad)" "$1"

With that in place, both the "bootstrap" binary (on startup) and the running binary (when you use the recompile command) will build the executable and symlink the binary thus produced by cabal-install to the destination expected by XMonad (which gets passed as $1).


The setup described just above for XMonad can also be used for xmobar, if you're compiling your configuration by using it as a library. In the xmobar configuration directory (the default is ~/.config/xmobar), put your xmobar.hs, create a .cabal file...

cabal-version:       3.4
name:                xmobar-duplode
version:             0.1.0.0

executable xmobar
  main-is:             xmobar.hs
  build-depends:       base
                     , xmobar

... optionally, a cabal.project file (it can be used to enable optional features of the xmobar library)...

packages: .

-- Leave out if you don't care about the compiler version.
with-compiler: ghc-9.4.8

-- Note the package in this stanza is xmobar, the library dependency,
-- and not xmobar-duplode, the executable with the configuration.
-- See the xmobar Hackage docs for the list of flags.
package xmobar
  flags: +all_extensions

... and a build script...

#!/bin/sh
cd "$HOME/.config/xmobar" && cabal build || exit
ln -sfT "$(cabal list-bin xmobar)" "$1"

... then create the "bootstrap" binary by running cabal install xmobar outside of ~/.config/xmobar, so that cabal will install Hackage xmobar rather than your custom one -- installing the "bootstrap" binary from Hackage is the easier option in the case of xmobar.

Mayle answered 15/1, 2024 at 2:56 Comment(29)
will keep being built with the specified GHC version, I guess I don't think in these terms as I usually nuke all but the most recent GHC I have, ahah. I guess I should refer to When you feel like trying out. Regarding if some change [...] ever requires you to tweak, I'm think of "something happened that will cause a compile-time failure"; if that's what you allude to, then why have to do it before being able to recompile it? If I try recompiling it, don't I just get a compilation error telling me what's wrong, reminding me that I have to "sanitize" my xmonad.hs/xmobar.hs?Distefano
Ok, XMonad works. But my xmobar is gone :( How do I handle this? Can I do something for the xmobar executable to be generated as part of building this project? Or should I make a similar ad-hoc project for it and then have xmonad.hs just spawn the xmobar executable? This latter idea feels a bit wrong, as it would require recompiling 2 things. Oh, when I say xmobar, I'm referring to the approach of using it as a library, i.e. having an actual Haskell xmobar.hs file, rather than an xmobarrc config file.Distefano
Ok, I've definitely screwed everything up :D I also delete ~/.cabal thinking it would be re-generated by re-installing cabal and then doing cabal update. :/Distefano
I've rerun cabal update, cabal install --package-env=$HOME/.config/xmonad --lib base xmonad xmonad-contrib and cabal install --package-env=$HOME/.config/xmonad xmonad, deleting existing files that caused errors (I guess I could have equally used --force-reinstalls and --overwrite-policy=always), but I don't seem to be able to get back to a working XMonad. Currently I get xmonad not in $PATH: ....Distefano
@Distefano Okay, let's begin from the end. Given you're using cabal install, I assume you're following the steps from the XMonad user guide, as opposed to those in this answer. Since you have deleted ~/.cabal and (I presume) are using a very recent cabal, it might be putting things in the XDG standard directories rather than recreating ~/.cabal. That being so, I suggest checking if the xmonad binary produced by the second cabal install is in ~/.local/bin, and if ~/.local/bin is in your path.Mayle
you're following the steps from the XMonad user guide, as opposed to those in this answer, that's just an attempt to get back to what worked a few hours ago. Eventaully I'd like to try use your approach (for which I miss the part explained in my second comment above). But for now I'd also like to understand what the heck I have done. Yes, the second cabal install ends with Symlinking 'xmonad' to '/home/enrico/.local/bin/xmonad'.Distefano
And I have export PATH="$HOME/.cabal/bin/xmonad:$PATH" in my ~/.bashrc. This makes me think that the ~/.cabal directory I had... was probably a remnant of who knows how long ago? I have now cabal 3.10.2.1, but maybe when I installed it the first time, long ago, it used to put things in ~/.cabal?Distefano
@Distefano Yeah -- the switch to XDG directories is fairly recent, I believe, and even the newer versions will keep using ~/.cabal if that directory exists, so that's likely what happened. Anyway, given there indeed is a ~/.local/bin/xmonad, adding ~/.local/bin to your path should get you back to the setup you had previously.Mayle
That's the same I thought, but upon trying to re-build xmonad (via Ctrl+q) I get the error xmonad not in $PATH: ..., with the path being the old one. How do I make xmonad digest my new PATH as defined in the ~/.bashrc?Distefano
@Distefano This sounds like xmonad issue #400: ~/.bashrc is usually not read by XMonad or other windowing systems. I suggest setting adding ~/.local/bin to PATH in ~/.bash_profile instead -- that's what I do here, following the Arch wiki.Mayle
Oh, right, I source .bashrc in .bash_profile, that's why it is available when I start X! Anyway, I'm not at the point that XMonad works, but xmobar.hs is not picked up, because Could not find module ‘Xmobar’ It is not a module in the current program, or in any known package., and if I try cabal install xmonad (with or without --package-env=$HOME/.config/xmonad and/or --lib) I get this error.Distefano
Oh, I had that problem in your error while getting my XMonad setup to work again. It boils down to cabal issue #9608, and has nothing to do with anything we're doing. The workarounds, while we wait for the patches to land, are described in the issue: either downgrade libvpl to openvpl in Arch, or edit /usr/lib/pkgconfig/vpl.pc in your system to remove the non-ASCII (R) characters.Mayle
(The butterfly wings here are openvpl getting repackaged as libvpl in a recent Arch update. libvpl has a buggy pkg-config file which cabal fails to parse. As a consequence, cabal can't install pango, which is configured using pkg-config and is a dependency of current xmobar. cabal then tries to fall back to a very old xmobar version (0.14) that doesn't depend on pango, but that one is so old that it won't build with recent GHCs, leading to the errors you saw.)Mayle
Thanks for that!! Now I'm back to where I was, and hopefully I can try using your approach instead, but without to much risk as I should be able to fix again if I break it again :DDistefano
Ok, so at the moment I've made ~/.config/xmonad a symbolic link to a directory with what I had before screwing everything up, and I can easily re-point it to a directory where I'm trying your approach. The trouble I'm having in the latter case, is the one I explained in my second comment to this answer. Do you know how can I deal with that?Distefano
@Distefano (1) While my usual setup doesn't use xmobar-as-a-library, it turns out you can use pretty much the same setup with a .cabal file alongside xmobar.hs in ~/.config/xmobar. I have updated the answer to cover that. (2) xmobar is a separate executable, with its own XMonad-like recompilation mechanism that will look for its own recompiling script that's also called build. That being so, I don't think keeping the two configurations in the same project will be worth the trouble.Mayle
Oh, this seems to work pretty nicely. I guess my original concern that This latter idea feels a bit wrong, as it would require recompiling 2 things is easily solved, I just need to rebind the M-q key to not just recompile and relaunch XMonad using but also recompile XMobar!Distefano
@Distefano I don't think you actually need to rebind M-q, it should work straight away. Assuming you're spawning xmobar from xmonad.hs, restarting XMonad will also restart xmobar, which, in turn, will lead to xmobar being recompiled.Mayle
XMonad does restart XMobar, as I can see by printing pidof xmobar before and after a restart of XMonad, obtaining a difference PID. However, if I change xmobar.hs, simply kill $(pidof xmobar) && xmobar & shows that the change is not absorbed, unless I do ./build ~/.local/bin/xmobar before restarting. The way I spawn XMobar from XMonad is withEasySB (statusBarProp (unwords ["xmobar", xmobarhs]) (pure myXmobarPP)) toggleStrutsKey, where toggleStrutsKey XConfig { modMask = m } = (m, xK_b) and xmobarhs = "~/.local/xmobar/xmobar.hs".Distefano
I guess you were referring to when running the system-wide xmobar, it will notice that you have your own implementation and (re)compile and run it as needed.Distefano
@Distefano [1/2] You're right, I misinterpreted some of what I saw yesterday. The underlying complication, I think, is that while the xmonad function from XMonad.Main handles recompiling with the build script, its analogue xmobar from Xmobar.App.Main doesn't, and so main = xmobar config isn't enough to make recompiling work.Mayle
@Distefano [2/2] The easiest way out, then, indeed is installing the "bootstrap" xmobar executable from Hackage, as it already covers that (via xmobarMain from Xmobar.App.Main). Remove the xmobar binary from ~/.local/main, and, if you don't already have the Hackage one in ~/.cabal/bin, run cabal install xmobar (no --lib, no --package-env) from anywhere except ~/.config/xmobar (so that cabal won't pick your custom xmobar).Mayle
(I'm assuming you meant Remove the xmobar binary from ~/.local/bin.) Last suggestion doesn't quite cut it yet. Upon doing cabal install xmobar from ~, I see that ls -l ~/.local/bin/xmobar has changed pointee, so I know the build worked. And clearly if I reload XMonad, I see in the bar whatever change I made to xmobar.hs before running the aforementioned cabal command line. However, if I then modify xmobar.hs again and re-reload XMonad, the changes are not shown, meaning that the build script is not rerunning, I guess.Distefano
Incidentally (and this is the first time I try) I'm seeing that starting from a "normal" state, everytime I re-building XMonad twice fast enough... one xmonad process is killed, and 2 are created. So if I keep doing it, I end up spawning a lot of them :|Distefano
I think the key is in the comment: the xmonad executable is older than @xmonad.hs@ or any file in the @lib@ directory (under the configuration directory) in XMonad.Core. I guess when you wrote I misinterpreted you were rolling back the suggestion of having two separate projects, and I should probably just put the xmobar.hs file in a lib subdirectory beside xmonad.hs. I'll give a try.Distefano
Let us continue this discussion in chat.Mayle
But so we run cabal install xmobar to install xmobar from Hackage, which results in /home/enrico/.local/bin/xmobar (which is what which xmobar echoes, given that dir is in the PATH) to point to the ../state/cabal/store/ghc-9.4.8/xmobar-0.47.3-e-xmobar-9fe877c28e834941690ce32ec183246fa150137c8bb0ee520a0b5039f0a0ebd5/bin/xmobar executable which is able to handle the recompilation thing. But then the build script will symlink /home/enrico/.config/xmobar/xmobar to dist-newstyle/build/x86_64-linux/ghc-9.4.8/xmobar-aster-0.1.0.0/x/xmobar/build/xmobar/xmobar. Why/how is that used?Distefano
As in, if we need the Hackage binary because it can deal with recompilation, then why isn't my xmobar config consisting just of xmobar.hs? Why do I have a whole cabal package around it?Distefano
@Distefano The Hackage binary includes the code which detects changes to your xmobar.hs, recompiles it if needed, and launches the resulting executable from ~/.config/xmobar/xmobar -- see Xmobar.App.Main. In principle, you could bring all of that into your code, adapting it so that the Hackage binary is no longer needed, but it's probably easier to just rely on the Hackage binary for initialisation.Mayle

© 2022 - 2025 — McMap. All rights reserved.