I saw an introduction to GUI programming in Haskell by Vladislav Zavialov. In his video, he referred to Unix-like operating systems but not Windows. I wanted to reproduce his example on Windows. As is often the case, it was not straightforward.
A test project
I created a new project gtkTest
using stack new gtkTest
and changed to its root directory with cd gtkTest
.
The Stack-supplied MSYS2 is found in a directory at the output of stack path --programs
. On my machine, it is in directory msys2-20210604
. I made sure that MSYS2 was up to date by running stack exec -- pacman -Syu
repeatedly until it reported everything was up to date and there was nothing to do.
Following the guide in the haskell-gi
wiki, I set two environment variables to the absolute path to directories in the Stack-supplied MSYS2: PKG_CONFIG_PATH
to mingw64\lib\pkgconfig
and XDG_DATA_DIRS
to mingw64\share
. I knew that Stack would already set PATH
in its stack exec
environment.
By default, the pkg-config
tool looks for .pc
files in the directories yielded by stack exec -- pkg-config --variable pc_path pkg-config
, which are ../lib/pkgconfig; ../share/pkgconfig
. PKG_CONFIG_PATH
provides a list of paths to search first, before searching the defaults.
I tried to install MSYS2 pkg-config
as a necessary dependency, but discovered that I already had pkgconfig
, which is an alternative.
I installed MSYS2 gobject-introspection
with stack exec -- pacman - S mingw-w64-x86_64-gobject-introspection
.
The GTK project had moved on since I had last investigated it. The latest stable version of GTK was 4.6.5. GTK version 3.24.34 was described as the latest ‘old’ stable version. I first installed GTK3 (version 3.24.34-1) and its dependencies with stack exec -- pacman -S mingw-w64-x86_64-gtksourceview4
. mingw-w64-x86_64-gtk3
is a dependency of gtksourceview4
.
hello, world
The haskell-gi
wiki provides its own example program, namely:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
{-# LANGUAGE LexicalNegation #-} {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedStrings #-} module Main (main) where -- From package haskell-gi-base import Data.GI.Base (AttrOp ((:=)), new, on, set) -- From package gi-gtk import qualified GI.Gtk as Gtk main :: IO () main = do Gtk.init Nothing win <- new Gtk.Window [ #title := "Hi there" ] on win #destroy Gtk.mainQuit button <- new Gtk.Button [ #label := "Click me" ] on button #clicked (set button [ #sensitive := False, #label := "Thanks for clicking me" ]) #add win button #setDefaultSize win 400 -1 #showAll win Gtk.main |
Using Hpack, the package.yaml
is:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
name: gtkTest version: 0.1.0.0 dependencies: - base >= 4.7 && < 5 - haskell-gi-base - gi-gtk executables: gtkTest: main: Main.hs source-dirs: app ghc-options: - -threaded - -rtsopts - -with-rtsopts=-N |
I used a stack.yaml
file based on GHC 9.0.2, namely:
1 2 3 4 |
# GHC 9.0.2 and gi-gtk-3.0.38 resolver: lts-19.13 packages: - . |
Stack’s resolver provided the latest version of gi-gtk
on Hackage in the 3.0
series. There is a separate 4.0
series of gi-gtk
versions, that provide bindings to GTK4.
stack build
and stack exec gtkTest
yield the expected GUI application:
GTK4
So far, so good. The complexity started with GTK4. By analogy with GTK3, I installed mingw-w64-x86_64-gtksourceview5
. As well as version 4.6.5-1 of gtk4
, this installed mingw-w64-x86_64-crt-git-10.0.0.r32.g89bacd2be-1
as one of its many dependencies.
Now, stack build
failed during linking, with:
1 2 3 4 5 6 7 8 |
Linking .stack-work\dist\d53b6a14\build\gtkTest\gtkTest.exe ... C://Users//mikep//AppData//Local//Programs//stack//x86_64-windows//ghc-9.0.2//mingw//bin/ld.exe: C:/Users/mikep/AppData/Local/Programs/stack/x86_64-windows/ghc-9.0.2/mingw/bin/../lib/gcc/x86_64-w64-mingw32/10.2.0/../../../../x86_64-w64-mingw32/lib/../lib/crt2.o:crtexe.c:(.rdata$.refptr.mingw_app_type[.refptr.mingw_app_type]+0x0): undefined reference to `mingw_app_type' C://Users//mikep//AppData//Local//Programs//stack//x86_64-windows//ghc-9.0.2//mingw//bin/ld.exe: C:/Users/mikep/AppData/Local/Programs/stack/x86_64-windows/ghc-9.0.2/mingw/bin/../lib/gcc/x86_64-w64-mingw32/10.2.0/../../../../x86_64-w64-mingw32/lib/../lib/crt2.o:crtexe.c:(.rdata$.refptr.mingw_initcharmax[.refptr.mingw_initcharmax]+0x0): undefined reference to `mingw_initcharmax' C://Users//mikep//AppData//Local//Programs//stack//x86_64-windows//ghc-9.0.2//mingw//bin/ld.exe: C:/Users/mikep/AppData/Local/Programs/stack/x86_64-windows/ghc-9.0.2/mingw/bin/../lib/gcc/x86_64-w64-mingw32/10.2.0/../../../../x86_64-w64-mingw32/lib/../lib/crt2.o:crtexe.c:(.rdata$.refptr.mingw_initltssuo_force[.refptr.mingw_initltssuo_force]+0x0): undefined reference to `mingw_initltssuo_force' C://Users//mikep//AppData//Local//Programs//stack//x86_64-windows//ghc-9.0.2//mingw//bin/ld.exe: C:/Users/mikep/AppData/Local/Programs/stack/x86_64-windows/ghc-9.0.2/mingw/bin/../lib/gcc/x86_64-w64-mingw32/10.2.0/../../../../x86_64-w64-mingw32/lib/../lib/crt2.o:crtexe.c:(.rdata$.refptr.mingw_initltsdyn_force[.refptr.mingw_initltsdyn_force]+0x0): undefined reference to `mingw_initltsdyn_force' C://Users//mikep//AppData//Local//Programs//stack//x86_64-windows//ghc-9.0.2//mingw//bin/ld.exe: C:/Users/mikep/AppData/Local/Programs/stack/x86_64-windows/ghc-9.0.2/mingw/bin/../lib/gcc/x86_64-w64-mingw32/10.2.0/../../../../x86_64-w64-mingw32/lib/../lib/crt2.o:crtexe.c:(.rdata$.refptr.mingw_initltsdrot_force[.refptr.mingw_initltsdrot_force]+0x0): undefined reference to `mingw_initltsdrot_force' collect2.exe: error: ld returned 1 exit status `gcc.exe' failed in phase `Linker'. (Exit code: 1) |
The undefined references are to symbols mingw_app_type
, mingw_initcharmax
, mingw_initltssuo_force
, mingw_initltsdyn_force
and mingw_initltsdrot_force
. lib\crt2.o
is a file provided by MSYS2 mingw-w64-x86_64-crt-git
.
It transpires that bits of MSYS2 used by versions of GHC before 9.4.1 conflict with the latest mingw-w64-x86_64-crt-git
. To avoid the conflict, it was necessary to roll back with stack exec -- pacman -Rcns mingw-w64-x86_64-gtksourceview5
.
GHC 9.4.1-alpha3
GHC 9.4.1 is still in alpha. However, I was able to use GTK4 on Windows with Stack and GHC 9.4.1-alpha3.
I caused Stack to download and install GHC 9.4.1-alpha3 with a local my-stack-setup.yaml
and the stack --setup-info-yaml=my-stack-setup.yaml
option:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
ghc: windows64: 9.4.1: url: "https://downloads.haskell.org/ghc/9.4.1-alpha3/ghc-9.4.0.20220623-x86_64-unknown-mingw32.tar.xz" content-length: 281104480 sevenzexe-info: url: "https://github.com/fpco/minghc/blob/master/bin/7z.exe?raw=true" content-length: 73728 sha1: 187dff8a3327da87050f678579816e5bae40c632 sevenzdll-info: url: "https://github.com/fpco/minghc/blob/master/bin/7z.dll?raw=true" content-length: 447488 sha1: 168a288d4456f0473f66e86a2d6fec4eb6f4b993 |
I modified the example program for the GTK4 API:
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 |
{-# LANGUAGE LexicalNegation #-} {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedStrings #-} module Main (main) where -- From package haskell-gi-base import Data.GI.Base (AttrOp ((:=)), new, on, set) -- From package gi-gtk import qualified GI.Gtk as Gtk import Control.Monad (void) main :: IO () main = do app <- new Gtk.Application [] on app #activate (activate app) void $ #run app Nothing activate :: Gtk.Application -> IO () activate app = do win <- new Gtk.Window [ #title := "Hi there" ] button <- new Gtk.Button [ #label := "Click me" ] on button #clicked (set button [ #sensitive := False, #label := "Thanks for clicking me" ]) #setChild win (Just button) #setDefaultSize win 400 -1 #show win #addWindow app win |
The associated package.yaml
was:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
name: gtkTest version: 0.1.0.0 dependencies: - base >= 4.17 && < 5 - gi-gtk >= 4 - haskell-gi-base - vector < 0.13 executables: gtkTest: main: Main.hs source-dirs: app ghc-options: - -threaded - -rtsopts - -with-rtsopts=-N |
GHC 9.4.1 ships with base-4.17.0.0
, which is not yet on Hackage. I discovered that vector-algorithms
is incompatible with the latest version of vector
, so I specified vector < 0.13
.
For the stack.yaml
resolver, I could use ghc-9.4.1
but this meant that I had to specify all the dependencies not provided with the compiler.
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 |
resolver: ghc-9.4.1 packages: - . allow-newer: true extra-deps: - ansi-terminal-0.11.3 - async-2.2.4 - attoparsec-0.14.4 - blaze-builder-0.4.2.2 - blaze-html-0.9.1.2 - blaze-markup-0.8.2.8 - cabal-doctest-1.0.9 - colour-2.3.6 - conduit-1.3.4.2 - conduit-extra-1.3.6 - data-default-class-0.1.2.0 - gi-atk-2.0.24 - gi-cairo-1.0.26 - gi-freetype2-2.0.1 - gi-gdk-4.0.4 - gi-gdkpixbuf-2.0.28 - gi-gio-2.0.29 - gi-glib-2.0.26 - gi-gmodule-2.0.2 - gi-gobject-2.0.27 - gi-graphene-1.0.4 - gi-gsk-4.0.4 - gi-gtk-4.0.5 - gi-harfbuzz-0.0.6 - gi-pango-1.0.26 - happy-1.20.0 - hashable-1.4.0.2 - haskell-gi-0.26.0 - haskell-gi-base-0.26.0 - haskell-gi-overloading-1.0 - haskell-lexer-1.1 - integer-logarithms-1.0.3.1 - mintty-0.1.4 - mono-traversable-1.0.15.3 - network-3.1.2.7 - pretty-show-1.10 - primitive-0.7.4.0 - random-1.2.1.1 - regex-base-0.94.0.2 - regex-tdfa-1.3.1.3 - resourcet-1.2.6 - safe-0.3.19 - scientific-0.3.7.0 - split-0.2.3.4 - splitmix-0.1.0.4 - streaming-commons-0.2.2.4 - typed-process-0.2.10.1 - unliftio-core-0.2.0.1 - unordered-containers-0.2.19.1 - vector-0.12.3.1 - vector-algorithms-0.8.0.4 - vector-stream-0.1.0.0 - xdg-basedir-0.2.2 - xml-conduit-1.9.1.1 - xml-types-0.3.8 - zlib-0.6.3.0 |
I discovered that I needed to use allow-newer: true
. I also discovered that forced stack build
to use a version of Win32
before 2.13.1 and that mintty
needed to be told that expressly. So, I used stack build --flag mintty:-win32-2-13-1
. That yielded (in part):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
Stack has not been tested with GHC versions above 9.0, and using 9.4.1, this may fail Stack has not been tested with Cabal versions above 3.4, but version 3.8.0.20220526 was found, this may fail Building all executables for `gtkTest' once. After a successful build of all of them, only specified executables will be rebuilt. gtkTest> configure (exe) Configuring gtkTest-0.1.0.0... gtkTest> build (exe) Preprocessing executable 'gtkTest' for gtkTest-0.1.0.0.. Building executable 'gtkTest' for gtkTest-0.1.0.0.. [1 of 3] Compiling Main [2 of 3] Compiling Paths_gtkTest [3 of 3] Linking .stack-work\\dist\\0ef4d674\\build\\gtkTest\\gtkTest.exe Warning: Failed to decode module interface: C:\Users\mikep\Documents\Code\Haskell\gtkTest\.stack-work\dist\0ef4d674\build\gtkTest\gtkTest-tmp\Paths_gtkTest.hi Decoding failure: not enough bytes Warning: Failed to decode module interface: C:\Users\mikep\Documents\Code\Haskell\gtkTest\.stack-work\dist\0ef4d674\build\gtkTest\gtkTest-tmp\Main.hi Decoding failure: not enough bytes gtkTest> copy/register Installing executable gtkTest in C:\Users\mikep\Documents\Code\Haskell\gtkTest\.stack-work\install\9c220015\bin |
Oddly, the directory in which gtkTest.exe
was installed was not the one added to the PATH
in the stack exec
environment. To run the executable, I had to give the path to it:
1 |
stack exec -- C:\Users\mikep\Documents\Code\Haskell\gtkTest\.stack-work\install\9c220015\bin\gtkTest |
The command yielded the expected GUI application: