File: scripts.md

package info (click to toggle)
haskell-stack 2.15.7-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 3,568 kB
  • sloc: haskell: 37,057; makefile: 6; ansic: 5
file content (268 lines) | stat: -rw-r--r-- 9,491 bytes parent folder | download
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
<div class="hidden-warning"><a href="https://docs.haskellstack.org/"><img src="https://cdn.jsdelivr.net/gh/commercialhaskell/stack/doc/img/hidden-warning.svg"></a></div>

# Stack's script interpreter

Stack offers a very useful feature for running files: a script interpreter. For
too long have Haskellers felt shackled to bash or Python because it's just too
hard to create reusable source-only Haskell scripts. Stack attempts to solve
that.

You can use `stack <file_name>` to execute a Haskell source file. Usually, the
Stack command to be applied is specified using a special Haskell comment (the
Stack interpreter options comment) at the start of the source file. That command
is most often `stack script` but it can be, for example, `stack runghc`. If
there is no Stack interpreter options comment, Stack will warn that one was
expected.

An example will be easiest to understand. Consider the Haskell source file
`turtle-example.hs` with contents:

~~~haskell
#!/usr/bin/env stack
-- stack script --snapshot lts-22.21 --package turtle
{-# LANGUAGE OverloadedStrings #-}
import Turtle (echo)
main = echo "Hello World!"
~~~

=== "Unix-like"

    The first line beginning with the 'shebang' (`#!`) tells Unix to use Stack
    as a script interpreter, if the file's permissions mark it as executable. A
    shebang line is limited to a single argument, here `stack`.

    The file's permissions can be set with command `chmod` and then it can be
    run:

    ~~~text
    chmod +x turtle-example.hs
    ./turtle-example.hs
    ~~~

    !!! note

        On macOS:

        - Avoid `{-# LANGUAGE CPP #-}` in Stack scripts; it breaks the shebang
          line ([GHC #6132](https://gitlab.haskell.org/ghc/ghc/issues/6132))

        - Use a compiled executable, not another script, in the shebang line.
          Eg `#!/usr/bin/env runhaskell` will work but
          `#!/usr/local/bin/runhaskell` would not.

    Alternatively, the script can be run with command:

    ~~~text
    stack turtle-example.hs
    ~~~

=== "Windows (with PowerShell)"

    The first line beginning with the 'shebang' (`#!`) has a meaning on
    Unix-like operating systems but will be ignored by PowerShell. It can be
    omitted on Windows. The script can be run with command:

    ~~~text
    stack turtle-example.hs
    ~~~

In both cases, the command yields:

~~~text
Hello World!
~~~

the first time after a little delay (as GHC is downloaded, if necessary, and
dependencies are built) and subsequent times more promptly (as the runs are
able to reuse everything already built).

The second line of the source code is the Stack interpreter options comment. In
this example, it specifies the `stack script` command with the options of a
LTS Haskell 22.21 snapshot (`--snapshot lts-22.21`) and ensuring the
[`turtle` package](https://hackage.haskell.org/package/turtle) is available
(`--package turtle`). The version of the package will be that in the specified
snapshot (`lts-22.21` provides `turtle-1.6.2`).

## Arguments and interpreter options and arguments

Arguments for the script can be specified on the command line after the file
name: `stack <file_name> <arg1> <arg2> ...`.

The Stack interpreter options comment must specify what would be a single valid
Stack command at the command line if the file name were included as an argument,
starting with `stack`. It can include `--` followed by arguments. In particular,
the Stack command `stack <arg1> MyScript.hs <arg4>` with
Stack interpreter options comment:

~~~haskell
-- stack <arg2> <command> <arg3> -- <arg5>
~~~

is equivalent to the following command at the command line:

~~~text
stack <arg1> <arg2> <command> <arg3> -- MyScript.hs <arg4> <arg5>
~~~

The Stack interpreter options comment must be the first line of the file, unless
a shebang line is the first line, when the comment must be the second line. The
comment must start in the first column of the line.

When many options are needed, a block style comment that splits the command over
more than one line may be more convenient and easier to read.

For example, the command `stack MyScript.hs arg1 arg2` with `MyScript.hs`:

~~~haskell
#!/usr/bin/env stack
{- stack script
   --snapshot lts-22.21
   --
   +RTS -s -RTS
-}
import Data.List (intercalate)
import System.Environment (getArgs)
import Turtle (echo, fromString)

main = do
  args <- getArgs
  echo $ fromString $ intercalate ", " args
~~~

is equivalent to the following command at the command line:

~~~text
stack script --snapshot lts-22.21 -- MyScript.hs arg1 arg2 +RTS -s -RTS
~~~

where `+RTS -s -RTS` are some of GHC's
[runtime system (RTS) options](https://downloads.haskell.org/~ghc/latest/docs/users_guide/runtime_control.html).

## Just-in-time compilation

As with using `stack script` at the command line, you can pass the `--compile`
flag to make Stack compile the script, and then run the compiled executable.
Compilation is done quickly, without optimization. To compile with optimization,
pass the `--optimize` flag instead. Compilation is done only if needed; if the
executable already exists, and is newer than the script, Stack just runs the
executable directly.

This feature can be good for speed (your script runs faster) and also for
durability (the executable remains runnable even if the script is disturbed, eg
due to changes in your installed GHC/snapshots, changes to source files during
git bisect, etc.)

## Using multiple packages

As with using `stack script` at the command line, you can also specify multiple
packages, either with multiple `--package` options, or by providing a comma or
space separated list. For example:

~~~haskell
#!/usr/bin/env stack
{- stack script
   --snapshot lts-22.21
   --package turtle
   --package "stm async"
   --package http-client,http-conduit
-}
~~~

## Stack configuration for scripts

With the `stack script` command, all Stack YAML configuration files (global and
project-level) are ignored.

With the `stack runghc` command, if the current working directory is inside a
project then that project's Stack project-level YAML configuration is effective
when running the script. Otherwise the script uses the global project
configuration specified in `<Stack root>/global-project/stack.yaml`.

## Testing scripts

You can use the flag `--script-no-run-compile` on the command line to enable (it
is disabled by default) the use of the `--no-run` option with `stack script`
(and forcing the `--compile` option). The flag may help test that scripts
compile in CI (continuous integration).

For example, consider the following simple script, in a file named `Script.hs`,
which makes use of the joke package
[`acme-missiles`](https://hackage.haskell.org/package/acme-missiles):

~~~haskell
{- stack script
   --snapshot lts-22.21
   --package acme-missiles
-}
import Acme.Missiles (launchMissiles)

main :: IO ()
main = launchMissiles
~~~

The command `stack --script-no-run-compile Script.hs` then behaves as if the
command
`stack script --snapshot lts-22.21 --package acme-missiles --no-run --compile -- Script.hs`
had been given. `Script.hs` is compiled (without optimisation) and the resulting
executable is not run: no missiles are launched in the process!

## Writing independent and reliable scripts

The `stack script` command will automatically:

* Install GHC and libraries, if missing. `stack script` behaves as if the
  `--install-ghc` flag had been passed at the command line.
* Require that all packages used be explicitly stated on the command line.

This ensures that your scripts are _independent_ of any prior deployment
specific configuration, and are _reliable_ by using exactly the same version of
all packages every time it runs so that the script does not break by
accidentally using incompatible package versions.

In earlier versions of Stack, the `runghc` command was used for scripts and can
still be used in that way. In order to achieve the same effect with the `runghc`
command, you can do the following:

1. Use the `--install-ghc` option to install the compiler automatically
2. Explicitly specify all packages required by the script using the `--package`
   option. Use `-hide-all-packages` GHC option to force explicit specification
   of all packages.
3. Use the `--snapshot` Stack option to ensure a specific GHC version and
   package set is used.

It is possible for configuration files to affect `stack runghc`. For that
reason, `stack script` is strongly recommended. For those curious, here is an
example with `runghc`:

~~~haskell
#!/usr/bin/env stack
{- stack
  runghc
  --install-ghc
  --snapshot lts-22.21
  --package base
  --package turtle
  --
  -hide-all-packages
  -}
~~~

The `runghc` command is still very useful, especially when you're working on a
project and want to access the package databases and configurations used by that
project. See the next section for more information on configuration files.

## Loading scripts in GHCi

Sometimes you want to load your script in GHCi to play around with your program.
In those cases, you can use `exec ghci` option in the script to achieve
it. Here is an example:

~~~haskell
#!/usr/bin/env stack
{- stack
   exec ghci
   --install-ghc
   --snapshot lts-22.21
   --package turtle
-}
~~~