summaryrefslogtreecommitdiff
path: root/haskell/cabal-lib.html
blob: 90bab1c76a8198cabf9feb7b75d4b60e17087628 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
<h2>About <code>cabal install --lib</code></h2>
<p><strong>TL;DR: Don't use it, add the library to your <code><em>package-name</em>.cabal</code> or <code>package.yaml</code> instead, or use a <a href="https://cabal.readthedocs.io/en/3.10/getting-started.html#run-a-single-file-haskell-script">cabal script</a>. After you learn more about the downsides, you can reconsider. See the &quot;What to do instead&quot; section below.</strong></p>
<hr />
<p>Suppose you are new to Haskell, or at least new to the current (2023) Haskell tooling, and would like to install a program written in Haskell.
For example, say you would like to install a Haskell formatter, say <code>fourmolu</code>, and find that installing Haskell packages uses a tool called <code>cabal</code>.
Hopeful, you try:</p>
<pre><code class="language-sh">cabal install fourmolu
</code></pre>
<p>and, if you are patient, this may well succeed and give you a <code>fourmolu</code> executable.</p>
<p>So now you want to write some Haskell!
But you want to use a library, say <code>brick</code>, for making a terminal user interface (TUI).
So you go:</p>
<pre><code class="language-sh">cabal install brick
</code></pre>
<p>which seems to proceed as before, compiling a bunch of dependencies.
(Note that in the past, this <em>was</em> a common way to install Haskell libraries for use in your own code, and quite a number of READMEs of older libraries still recommend this command.)
But at the end it prints this warning: (as of cabal-install 3.10.1.0)</p>
<pre><code>@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: Installation might not be completed as desired! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
The command &quot;cabal install [TARGETS]&quot; doesn't expose libraries.
* You might have wanted to add them as dependencies to your package. In this
case add &quot;brick&quot; to the build-depends field(s) of your package's .cabal file.
* You might have wanted to add them to a GHC environment. In this case use
&quot;cabal install --lib brick&quot;. The &quot;--lib&quot; flag is provisional: see
https://github.com/haskell/cabal/issues/6481 for more information.
</code></pre>
<p>which looks scary, using the same kind of <code>@@@@</code> banner as <code>ssh</code> reporting a possible man-in-the-middle attack (a changed host key, really).
But you want to use this library, after all, and you're just working in a single <code>.hs</code> file and aren't planning on creating a &quot;package&quot;.
So you try the second suggestion:</p>
<pre><code class="language-sh">cabal install --lib brick      # note, don't try this at home
</code></pre>
<p>and that seems to work -- and if you had let the previous <code>cabal install brick</code> command run to completion, it doesn't even seem to do much.
That much is true: it hasn't done much, but what it <em>has</em> done is probably not what you wanted.</p>
<p>For example, let's try to sanity-check our Haskell installation and start a REPL:</p>
<pre><code>$ ghci
Loaded package environment from /home/tom/.ghc/x86_64-linux-9.4.7/environments/default
GHCi, version 9.4.7: https://www.haskell.org/ghc/  :? for help
ghci&gt; 1 + 2

&lt;interactive&gt;:1:3: error:
    Variable not in scope: (+) :: t0 -&gt; t1 -&gt; t
ghci&gt; print &quot;hi&quot;

&lt;interactive&gt;:3:1: error:
    Variable not in scope: print :: base-4.17.2.0:GHC.Base.String -&gt; t
ghci&gt;
</code></pre>
<p>I mean, that doesn't look good, doesn't it?</p>
<p>And if that did not scare you enough, suppose that in the future, you want to use a newer version of <code>brick</code> and try to install that using <code>cabal install --lib brick</code> again.
What you'll see is this:</p>
<pre><code>$ cabal install --lib brick-1.9
Error: cabal: Packages requested to install already exist in environment file
at /home/tom/.ghc/x86_64-linux-9.4.7/environments/default. Overwriting them
may break other packages. Use --force-reinstalls to proceed anyway. Packages:
brick
</code></pre>
<p>(I simulated the situation by installing an older version instead. I can't time-travel, unfortunately.)</p>
<p>Or suppose that you now also want to use another library, say <code>eigen</code>:</p>
<pre><code>$ cabal install --lib eigen
Resolving dependencies...
Error: cabal: Could not resolve dependencies:
[__0] next goal: brick (user goal)
[__0] rejecting:
brick-1.10/installed-1f76dfaf75736c0f6e2a4a2cf992bb12da05f6bbc7985f9787547739947e4696
(package is broken, missing dependency
bimap-0.5.0-2ee7565ca29f0edc78a3c8fe09c9eef36c96d08473660eec8a944cef16ac4d86)
[__0] trying: brick-1.10
[__1] trying: vty-5.39 (dependency of brick)
...
...
</code></pre>
<p>The error message is much longer than this, but I cut it off to save some space.
Apparently it claims our installation of <code>brick</code> is actually broken, despite it working okay in <code>ghci</code>, but in any case this didn't work.</p>
<h2>What happened?</h2>
<p>Note the line printed by <code>ghci</code>:</p>
<pre><code>Loaded package environment from /home/tom/.ghc/x86_64-linux-9.4.7/environments/default
</code></pre>
<p>This file is the &quot;GHC environment&quot; from above that <code>cabal</code> wrote to.
It now contains this:</p>
<pre><code>clear-package-db
global-package-db
package-db /home/tom/.cabal/store/ghc-9.4.7/package.db
package-id brick-1.10-1f76dfaf75736c0f6e2a4a2cf992bb12da05f6bbc7985f9787547739947e4696
</code></pre>
<p>This means that when starting <code>ghci</code>, these, and no others, are the packages that are in scope:</p>
<pre><code>$ ghci
Loaded package environment from /home/tom/.ghc/x86_64-linux-9.4.7/environments/default
GHCi, version 9.4.7: https://www.haskell.org/ghc/  :? for help
ghci&gt; :show packages
active package flags:
  -package-id brick-1.10-1f76dfaf75736c0f6e2a4a2cf992bb12da05f6bbc7985f9787547739947e4696
</code></pre>
<p><em>This does not include <code>base</code>;</em> this is what produced the broken <code>ghci</code> above (which couldn't find <code>(+)</code> nor <code>print</code>).</p>
<p>You can &quot;fix&quot; this:</p>
<pre><code>ghci&gt; :set -package base
package flags have changed, resetting and loading new packages...
ghci&gt; 1 + 2
3
ghci&gt; :show packages
active package flags:
  -package base
  -package-id brick-1.10-1f76dfaf75736c0f6e2a4a2cf992bb12da05f6bbc7985f9787547739947e4696
</code></pre>
<p>and you can even make that change permanent with <code>cabal install --lib base</code>, which adds (in my case) a line <code>package-id base-4.17.2.0</code> to the aforementioned <code>default</code> file.</p>
<p>In short, a <em>GHC environment file</em> was created by <code>cabal</code> that contains a list of packages in scope for <code>ghc</code> when you're accessing <code>GHC</code> through <code>cabal</code> in the context of a project.
You need to either manage this file manually or through some <a href="https://github.com/phadej/cabal-extras">helper tool</a>, and you will need to, because <code>cabal</code> won't resolve conflicts for you.
You're back to manual, imperative management of dependencies.</p>
<p>Furthermore, this way of installing dependencies is fundamentally separate from the code that <em>uses</em> those dependencies, and is just one such list.
So if you have multiple scripts that you'd like to work globally, outside of a project, their dependency sets had better be compatible.</p>
<h2>How to fix the situation</h2>
<p>If you got yourself in a pickle due to an unintended use of <code>cabal install --lib</code>, you can undo its effects (apart from having used some disk space in compiling the packages in question) by removing the <code>default</code> file mentioned above.
This is in <code>~/.ghc/<em>architecture</em>-<em>OS</em>-<em>ghcversion</em>/environments/default</code>.</p>
<p>As mentioned, the compiled packages are still around (in <code>~/.cabal/store/ghc-<em>version</em>/</code>), but removing those is tricky -- do not try it, cabal likes to maintain its own consistent set of packages in the &quot;store&quot;.
Removing the entire store folder for a particular GHC version is safe, however -- even though this does of course mean that you may need to recompile a lot of things later. :)</p>
<h2>What to do instead</h2>
<p>Create a project!
The intended mode of operation of the modern Haskell tooling, that is <code>cabal</code> and <code>stack</code>, is to always work inside of a <em>project</em>.
Often, &quot;project&quot; basically means &quot;package&quot;, but you can have projects with multiple packages in them (using a <code>cabal.project</code> file, see <a href="https://cabal.readthedocs.io/en/3.10/cabal-project.html">the docs</a>).</p>
<p>Creating a package is easily done using <code>cabal init --simple</code> inside a fresh directory.
If you like to be asked more questions, you can also opt for <code>cabal init</code> instead.
Then, you can <strong>declaratively</strong> add dependencies in the <code>build-depends</code> field of your executable/library in the <code><em>package-name</em>.cabal</code> file that was generated.
Put a comma (<code>,</code>) between the package names in the <code>build-depends</code> field.</p>
<p>Note that if you selected &quot;Library&quot; and &quot;yes&quot; for generating a test suite (the default option), there will be <em>two</em> <code>build-depends</code> blocks in your <code><em>package-name</em>.cabal</code> file, one for each <em>component</em>.
A package (a single thing in the package repository, should you decide to upload it to Hackage at some point) can contain multiple components: possibly one public library, as well as zero or more executables, test suites, or benchmarks.
(There is also the concept of an &quot;internal library&quot;, for which see <a href="https://cabal.readthedocs.io/en/3.10/cabal-package.html#sublibs">the documentation</a>, but don't worry about that.)</p>
<p>You can start writing code in the <code>app/Main.hs</code> file (or <code>src/MyLib.hs</code> file if you selected Library) and run using <code>cabal run</code> (or build using <code>cabal build</code> in the case of a library).
<code>cabal</code> will automatically ensure that a consistent set of versions is compiled and made available, if at all possible.
You can add <a href="https://cabal.readthedocs.io/en/3.10/cabal-package.html#pkg-field-build-depends">version bounds</a> to your dependencies if you want to apply some proper software engineering principles.</p>
<p>(If you want to use <code>stack</code> instead of <code>cabal</code>, try <a href="https://docs.haskellstack.org/en/stable/GUIDE/">their getting started guide</a>.)</p>
<h3>An even lighter-weight alternative</h3>
<p>An alternative to creating a project is to make a <em>cabal script</em>: this allows you to effectively make a self-contained project inside a single Haskell file.
You specify the dependencies in a special comment block at the top of the file.
See <a href="https://cabal.readthedocs.io/en/3.10/getting-started.html#run-a-single-file-haskell-script">the documentation</a> for more details.</p>