aboutsummaryrefslogtreecommitdiff
path: root/ch23/support
diff options
context:
space:
mode:
Diffstat (limited to 'ch23/support')
-rw-r--r--ch23/support/README.md96
-rw-r--r--ch23/support/ch23.patch113
-rw-r--r--ch23/support/ch23.pngbin0 -> 572596 bytes
-rwxr-xr-xch23/support/main.sh360
-rw-r--r--ch23/support/patches/gtk-0.14.5.patch24
-rw-r--r--ch23/support/patches/gtk2hs-buildtools-0.13.5.0.patch12
-rw-r--r--ch23/support/patches/old-locale-1.0.0.7.patch49
7 files changed, 654 insertions, 0 deletions
diff --git a/ch23/support/README.md b/ch23/support/README.md
new file mode 100644
index 0000000..8744da0
--- /dev/null
+++ b/ch23/support/README.md
@@ -0,0 +1,96 @@
+# Support for Chapter 23: GUI programming
+
+This directory contains all that is needed to compile and run the application
+from the chapter 23.
+
+The starting point was that I wasn't able to install the `glade` hackage package
+in a recent Haskell environment, so I picked the most recent older GHC version
+the `glade` library was tested with (version 8.0.1) and aligned everything else
+to it.
+
+It was created and tested on Slackware64 15.0 Linux distribution, but it should
+be possible to use it as a guide for running the chapter 23 on other Linux
+distributions or maybe even other operating systems. For full reproducibility,
+Slackware64 15.0 is assumed.
+
+There was still a need to do minor changes to the code of the chapter. [The
+patch](ch23.patch) is included in this directory.
+
+## The result
+
+In the terminal, you can see patching the original code of the chapter 23,
+activating the environment, compiling and running the `PodLocalMain`
+application. There is also a GTK 2 Glade editor running.
+
+![Running PodLocalMain and GTK 2 Glade designer](ch23.png?raw=true)
+
+## The main.sh script
+
+The main script automates installation of the environment. It
+* downloads all binary and source archives needed
+* verifies SHA256 sum of every downloaded archive
+* installs the GHC compiler
+* installs libraries related to GTK 2
+* installs the Haskell packages
+* installs the Glade designer for GTK 2 for the full experience :-)
+* creates a bash script for activating the installed environment.
+
+The script installs everything in a single directory. It doesn't modify anything
+outside of it. Uninstallation is just removing that directory.
+
+## Steps
+
+The steps for getting the chapter 23 to run are
+1. Installing the environment
+2. Activating the environment
+3. Patching and compiling the application
+
+See the terminal in the screenshot above for the activation, patching, and compiling.
+
+### Installation
+
+1. Install Slackware64 15.0. The DVD image is at [the Slackware ISO
+mirrors](https://mirrors.slackware.com/slackware/slackware-iso/slackware64-15.0-iso/). It
+contains the software needed. I installed the system in a virtual machine. For
+simplicity I did full installation. It needs about 15 GB, so give the virtual
+system at least 25 GB to have enough space for the downloaded and installed
+files. If you are not a Slackware user, for booting into the graphical environment
+ 1. log in as root
+ 2. run `adduser` to add an unprivileged user
+ 3. edit `/etc/inittab` (e.g., by `nano` editor) and change the
+ `id:3:initdefault:` line to `id:4:initdefault:`
+ 4. reboot the system
+
+2. Get this directory. You can copy it into the virtual machine via a directory
+shared with the host OS or git-clone it from inside.
+4. Run the `main.sh` script inside this directory. It creates a root directory
+in the current directory and installs all files there.
+
+### Activation
+
+The activation script `env` is in the root directory created by the `main.sh` in
+`DESTDIR`. Source it in your bash session. It sets environment variables (PATH,
+LD_LIBRARY_PATH) to include and prefer the installed GHC version, libraries, and
+executables.
+
+This is done every time you want to compile and run the chapter 23. Running
+other programs may not work because of the overriden paths to the libraries. Run
+those in a normal shell session without the environment activated.
+
+### Patching the code of chapter 23
+
+Go to the directory with code for the chapter and run `patch -p1 < /path/to/the/ch23.patch`.
+
+### Compiling and running the application
+
+With the environment activated, run
+```
+> ghc PodLocalMain.sh
+> ./PodLocalMain
+```
+
+## The GTK 2 Glade designer
+
+The designer is run by `glade-3` command when the environment is activated. Note
+that running just `glade` command runs the GTK 3 Glade designer included in
+Slackware64 15.0 which cannot edit GTK 2 glade files.
diff --git a/ch23/support/ch23.patch b/ch23/support/ch23.patch
new file mode 100644
index 0000000..e287c73
--- /dev/null
+++ b/ch23/support/ch23.patch
@@ -0,0 +1,113 @@
+diff -rupN ch23/PodMainGUI.hs new-ch23/PodMainGUI.hs
+--- ch23/PodMainGUI.hs 2025-08-04 11:36:39.929319568 +0200
++++ new-ch23/PodMainGUI.hs 2025-08-04 09:03:27.606792410 +0200
+@@ -119,7 +119,7 @@ guiAdd gui dbh =
+ where procOK =
+ do url <- entryGetText (awEntry gui)
+ widgetHide (addWin gui) -- Remove the dialog
+- add dbh url -- Add to the DB
++ addUrl dbh url -- Add to the DB
+ {-- /snippet guiAdd --}
+
+ {-- snippet statusWindow --}
+@@ -184,7 +184,7 @@ guiFetch gui dbh =
+ {-- /snippet statusWindowFuncs --}
+
+ {-- snippet workerFuncs --}
+-add dbh url =
++addUrl dbh url =
+ do addPodcast dbh pc
+ commit dbh
+ where pc = Podcast {castId = 0, castURL = url}
+diff -rupN ch23/PodParser.hs new-ch23/PodParser.hs
+--- ch23/PodParser.hs 2025-08-04 11:36:39.930319562 +0200
++++ new-ch23/PodParser.hs 2025-08-03 20:50:15.690901819 +0200
+@@ -5,6 +5,8 @@ import PodTypes
+ import Text.XML.HaXml
+ import Text.XML.HaXml.Parse
+ import Text.XML.HaXml.Html.Generate(showattr)
++import Text.XML.HaXml.Posn
++import Text.XML.HaXml.Util
+ import Data.Char
+ import Data.List
+
+@@ -35,8 +37,8 @@ parse content name =
+ where parseResult = xmlParse name (stripUnicodeBOM content)
+ doc = getContent parseResult
+
+- getContent :: Document -> Content
+- getContent (Document _ _ e _) = CElem e
++ getContent :: Document Posn -> Content Posn
++ getContent d = docContent (posInNewCxt name Nothing) d
+
+ {- | Some Unicode documents begin with a binary sequence;
+ strip it off before processing. -}
+@@ -50,36 +52,36 @@ Note that HaXml defines CFilter as:
+
+ > type CFilter = Content -> [Content]
+ -}
+-channel :: CFilter
++channel :: CFilter Posn
+ channel = tag "rss" /> tag "channel"
+
+-getTitle :: Content -> String
++getTitle :: Content Posn -> String
+ getTitle doc =
+ contentToStringDefault "Untitled Podcast"
+ (channel /> tag "title" /> txt $ doc)
+
+-getEnclosures :: Content -> [Item]
++getEnclosures :: Content Posn -> [Item]
+ getEnclosures doc =
+ concatMap procItem $ getItems doc
+- where procItem :: Content -> [Item]
++ where procItem :: Content Posn -> [Item]
+ procItem item = concatMap (procEnclosure title) enclosure
+ where title = contentToStringDefault "Untitled Episode"
+ (keep /> tag "title" /> txt $ item)
+ enclosure = (keep /> tag "enclosure") item
+
+- getItems :: CFilter
++ getItems :: CFilter Posn
+ getItems = channel /> tag "item"
+
+- procEnclosure :: String -> Content -> [Item]
++ procEnclosure :: String -> Content Posn -> [Item]
+ procEnclosure title enclosure =
+ map makeItem (showattr "url" enclosure)
+- where makeItem :: Content -> Item
++ where makeItem :: Content Posn -> Item
+ makeItem x = Item {itemtitle = title,
+ enclosureurl = contentToString [x]}
+
+ {- | Convert [Content] to a printable String, with a default if the
+ passed-in [Content] is [], signifying a lack of a match. -}
+-contentToStringDefault :: String -> [Content] -> String
++contentToStringDefault :: String -> [Content Posn] -> String
+ contentToStringDefault msg [] = msg
+ contentToStringDefault _ x = contentToString x
+
+@@ -92,15 +94,18 @@ An implementation without unescaping wou
+ Because HaXml's unescaping only works on Elements, we must make sure that
+ whatever Content we have is wrapped in an Element, then use txt to
+ pull the insides back out. -}
+-contentToString :: [Content] -> String
++contentToString :: [Content Posn] -> String
+ contentToString =
+ concatMap procContent
+ where procContent x =
+- verbatim $ keep /> txt $ CElem (unesc (fakeElem x))
++ verbatim $ keep /> txt $ CElem (unesc (fakeElem x)) fakePosn
+
+- fakeElem :: Content -> Element
+- fakeElem x = Elem "fake" [] [x]
++ fakeElem :: Content Posn -> Element Posn
++ fakeElem x = Elem (N "fake") [] [x]
+
+- unesc :: Element -> Element
++ fakePosn :: Posn
++ fakePosn = (posInNewCxt "fakeName" Nothing)
++
++ unesc :: Element Posn -> Element Posn
+ unesc = xmlUnEscape stdXmlEscaper
+ {-- /snippet all --}
diff --git a/ch23/support/ch23.png b/ch23/support/ch23.png
new file mode 100644
index 0000000..65d56ee
--- /dev/null
+++ b/ch23/support/ch23.png
Binary files differ
diff --git a/ch23/support/main.sh b/ch23/support/main.sh
new file mode 100755
index 0000000..f248c28
--- /dev/null
+++ b/ch23/support/main.sh
@@ -0,0 +1,360 @@
+#!/bin/bash -x
+
+# Exit on any error
+set -e
+
+CWD=$(pwd)
+
+# Directory where all files created and modified by this script will be put
+ROOT=$CWD/ROOT
+
+DEST=$ROOT/DESTDIR
+DEST_PACKAGES=$DEST/packages
+
+TMP=$ROOT/TMP
+
+export CFLAGS="-O2 -fPIC"
+export CXXFLAGS="$CFLAGS"
+
+
+#------------------------------------------------------------------------------
+# Functions
+#------------------------------------------------------------------------------
+
+function download_sources()
+{
+ declare -A urls
+
+ # Binaries
+ urls[ghc-8.0.1-x86_64-deb8-linux.tar.xz]='https://downloads.haskell.org/~ghc/8.0.1/ghc-8.0.1-x86_64-deb8-linux.tar.xz'
+ urls[libtinfo5_6.4-4_amd64.deb]='http://ftp.cz.debian.org/debian/pool/main/n/ncurses/libtinfo5_6.4-4_amd64.deb'
+
+ # Non-Haskell source packages
+ urls[src/cairo-1.16.0.tar.xz]='https://www.cairographics.org/releases/cairo-1.16.0.tar.xz'
+ urls[src/glade3-3.8.6.tar.xz]='https://download.gnome.org/sources/glade3/3.8/glade3-3.8.6.tar.xz'
+ urls[src/glib-2.60.0.tar.gz]='https://download.gnome.org/sources/glib/2.60/glib-2.60.0.tar.xz'
+ urls[src/gobject-introspection-1.66.1.tar.xz]='https://download.gnome.org/sources/gobject-introspection/1.66/gobject-introspection-1.66.1.tar.xz'
+ urls[src/pango-1.43.0.tar.xz]='https://download.gnome.org/sources/pango/1.43/pango-1.43.0.tar.xz'
+
+ # Haskell source packages
+ urls[packages/HDBC-2.4.0.2.tar.gz]='https://hackage.haskell.org/package/HDBC-2.4.0.2/HDBC-2.4.0.2.tar.gz'
+ urls[packages/HDBC-sqlite3-2.3.3.1.tar.gz]='https://hackage.haskell.org/package/HDBC-sqlite3-2.3.3.1/HDBC-sqlite3-2.3.3.1.tar.gz'
+ urls[packages/HTTP-4000.4.1.tar.gz]='https://hackage.haskell.org/package/HTTP-4000.4.1/HTTP-4000.4.1.tar.gz'
+ urls[packages/HaXml-1.25.3.tar.gz]='https://hackage.haskell.org/package/HaXml-1.25.3/HaXml-1.25.3.tar.gz'
+ urls[packages/QuickCheck-2.8.2.tar.gz]='https://hackage.haskell.org/package/QuickCheck-2.8.2/QuickCheck-2.8.2.tar.gz'
+ urls[packages/alex-3.1.7.tar.gz]='https://hackage.haskell.org/package/alex-3.1.7/alex-3.1.7.tar.gz'
+ urls[packages/base-4.9.0.0.tar.gz]='https://hackage.haskell.org/package/base-4.9.0.0/base-4.9.0.0.tar.gz'
+ urls[packages/cairo-0.13.8.1.tar.gz]='https://hackage.haskell.org/package/cairo-0.13.8.1/cairo-0.13.8.1.tar.gz'
+ urls[packages/convertible-1.1.1.0.tar.gz]='https://hackage.haskell.org/package/convertible-1.1.1.0/convertible-1.1.1.0.tar.gz'
+ urls[packages/glade-0.13.1.tar.gz]='https://hackage.haskell.org/package/glade-0.13.1/glade-0.13.1.tar.gz'
+ urls[packages/glib-0.13.4.1.tar.gz]='https://hackage.haskell.org/package/glib-0.13.4.1/glib-0.13.4.1.tar.gz'
+ urls[packages/gtk-0.14.5.tar.gz]='https://hackage.haskell.org/package/gtk-0.14.5/gtk-0.14.5.tar.gz'
+ urls[packages/gtk2hs-buildtools-0.13.5.0.tar.gz]='https://hackage.haskell.org/package/gtk2hs-buildtools-0.13.5.0/gtk2hs-buildtools-0.13.5.0.tar.gz'
+ urls[packages/happy-1.19.5.tar.gz]='https://hackage.haskell.org/package/happy-1.19.5/happy-1.19.5.tar.gz'
+ urls[packages/hashable-1.2.4.0.tar.gz]='https://hackage.haskell.org/package/hashable-1.2.4.0/hashable-1.2.4.0.tar.gz'
+ urls[packages/hashtables-1.2.1.1.tar.gz]='https://hackage.haskell.org/package/hashtables-1.2.1.1/hashtables-1.2.1.1.tar.gz'
+ urls[packages/mtl-2.2.2.tar.gz]='https://hackage.haskell.org/package/mtl-2.2.2/mtl-2.2.2.tar.gz'
+ urls[packages/network-2.6.3.1.tar.gz]='https://hackage.haskell.org/package/network-2.6.3.1/network-2.6.3.1.tar.gz'
+ urls[packages/network-uri-2.6.4.2.tar.gz]='https://hackage.haskell.org/package/network-uri-2.6.4.2/network-uri-2.6.4.2.tar.gz'
+ urls[packages/old-locale-1.0.0.7.tar.gz]='https://hackage.haskell.org/package/old-locale-1.0.0.7/old-locale-1.0.0.7.tar.gz'
+ urls[packages/old-time-1.1.0.4.tar.gz]='https://hackage.haskell.org/package/old-time-1.1.0.4/old-time-1.1.0.4.tar.gz'
+ urls[packages/pango-0.13.2.0.tar.gz]='https://hackage.haskell.org/package/pango-0.13.2.0/pango-0.13.2.0.tar.gz'
+ urls[packages/parsec-3.1.15.0.tar.gz]='https://hackage.haskell.org/package/parsec-3.1.15.0/parsec-3.1.15.0.tar.gz'
+ urls[packages/polyparse-1.12.tar.gz]='https://hackage.haskell.org/package/polyparse-1.12/polyparse-1.12.tar.gz'
+ urls[packages/primitive-0.6.1.1.tar.gz]='https://hackage.haskell.org/package/primitive-0.6.1.1/primitive-0.6.1.1.tar.gz'
+ urls[packages/random-1.1.tar.gz]='https://hackage.haskell.org/package/random-1.1/random-1.1.tar.gz'
+ urls[packages/text-1.2.2.2.tar.gz]='https://hackage.haskell.org/package/text-1.2.2.2/text-1.2.2.2.tar.gz'
+ urls[packages/tf-random-0.5.tar.gz]='https://hackage.haskell.org/package/tf-random-0.5/tf-random-0.5.tar.gz'
+ urls[packages/th-compat-0.1.6.tar.gz]='https://hackage.haskell.org/package/th-compat-0.1.6/th-compat-0.1.6.tar.gz'
+ urls[packages/utf8-string-1.0.2.tar.gz]='https://hackage.haskell.org/package/utf8-string-1.0.2/utf8-string-1.0.2.tar.gz'
+ urls[packages/vector-0.12.0.0.tar.gz]='https://hackage.haskell.org/package/vector-0.12.0.0/vector-0.12.0.0.tar.gz'
+
+ for src in "${!urls[@]}"; do
+ if [ ! -f $ROOT/$src ]; then
+ dir=$ROOT/$(dirname $src)
+ [ ! -d $dir ] && mkdir -p $dir
+ wget -O $ROOT/$src ${urls[$src]}
+ fi
+ done
+}
+
+function verify_sources()
+{
+ declare -A sums
+
+ # Binaries
+ sums[ghc-8.0.1-x86_64-deb8-linux.tar.xz]='b1c06af49b29521d5b122ef3311f5843e342db8b1769ea7c602cc16d66098ced'
+ sums[libtinfo5_6.4-4_amd64.deb]='dd347f794e651039e7b4c391f86c674fed7f415b3dca6b0937beb0d470f09c1a'
+
+ # Non-Haskell source packages
+ sums[src/cairo-1.16.0.tar.xz]='5e7b29b3f113ef870d1e3ecf8adf21f923396401604bda16d44be45e66052331'
+ # This version of Glade designer is for GTK2
+ sums[src/glade3-3.8.6.tar.xz]='aaeeebffaeb3068fb23757a2eede46adeb4c7cecc740feed7654e065491f5449'
+ sums[src/glib-2.60.0.tar.gz]='20865d8b96840d89d9340fc485b4b1131c1bb24d16a258a22d642c3bb1b44353'
+ sums[src/gobject-introspection-1.66.1.tar.xz]='dd44a55ee5f426ea22b6b89624708f9e8d53f5cc94e5485c15c87cb30e06161d'
+ sums[src/pango-1.43.0.tar.xz]='d2c0c253a5328a0eccb00cdd66ce2c8713fabd2c9836000b6e22a8b06ba3ddd2'
+
+ # Haskell source packages
+ sums[packages/HDBC-2.4.0.2.tar.gz]='670757fd674b6caf2f456034bdcb54812af2cdf2a32465d7f4b7f0baa377db5a'
+ sums[packages/HDBC-sqlite3-2.3.3.1.tar.gz]='a783d9ab707ebfc68e3e46bd1bbb5d3d5493f50a7ccf31223d9848cff986ebea'
+ sums[packages/HTTP-4000.4.1.tar.gz]='df31d8efec775124dab856d7177ddcba31be9f9e0836ebdab03d94392f2dd453'
+ sums[packages/HaXml-1.25.3.tar.gz]='6448a7ee1c26159c6c10db93757ed9248f647b1c0c431e7aead6aadd6d2307c7'
+ sums[packages/QuickCheck-2.8.2.tar.gz]='98c64de1e2dbf801c54dcdcb8ddc33b3569e0da38b39d711ee6ac505769926aa'
+ sums[packages/alex-3.1.7.tar.gz]='89a1a13da6ccbeb006488d9574382e891cf7c0567752b330cc8616d748bf28d1'
+ sums[packages/base-4.9.0.0.tar.gz]='de577e8bd48de97be954c32951b9544ecdbbede721042c71f7f611af4ba8be2d'
+ sums[packages/cairo-0.13.8.1.tar.gz]='1316412d51556205cfc097a354eddf0e51f4d319cde0498626a2854733f4f3c2'
+ sums[packages/convertible-1.1.1.0.tar.gz]='e9f9a70904b9995314c2aeb41580d654a2c76293feb955fb6bd63256c355286c'
+ sums[packages/glade-0.13.1.tar.gz]='6bb9c72052085c83c1810f1389875d260b9d65f1ea4c4e64022270291ae9be45'
+ sums[packages/glib-0.13.4.1.tar.gz]='f57202ed4094cc50caa8b390c8b78a1620b3c43b913edb1e5bda0f3c5be32630'
+ sums[packages/gtk-0.14.5.tar.gz]='ffdfb54247dfbdf3b9936504802e3e0d2238cf5a0c145e745899d2c17f7c7001'
+ sums[packages/gtk2hs-buildtools-0.13.5.0.tar.gz]='e45f9b2f8a088a1c23b8d3618cbc765fb6a5a4bf1c8329bb513cdb18d9c14305'
+ sums[packages/happy-1.19.5.tar.gz]='62f03ac11d7b4b9913f212f5aa2eee1087f3b46dc07d799d41e1854ff02843da'
+ sums[packages/hashable-1.2.4.0.tar.gz]='fb9671db0c39cd48d38e2e13e3352e2bf7dfa6341edfe68789a1753d21bb3cf3'
+ sums[packages/hashtables-1.2.1.1.tar.gz]='227f554a93310645c654254659969b347de3d1bf3d98901dbb5c113ece72e951'
+ sums[packages/mtl-2.2.2.tar.gz]='8803f48a8ed33296c3a3272f448198737a287ec31baa901af09e2118c829bef6'
+ sums[packages/network-2.6.3.1.tar.gz]='57045f5e2bedc095670182130a6d1134fcc65d097824ac5b03933876067d82e6'
+ sums[packages/network-uri-2.6.4.2.tar.gz]='9c188973126e893250b881f20e8811dca06c223c23402b06f7a1f2e995797228'
+ sums[packages/old-locale-1.0.0.7.tar.gz]='dbaf8bf6b888fb98845705079296a23c3f40ee2f449df7312f7f7f1de18d7b50'
+ sums[packages/old-time-1.1.0.4.tar.gz]='1e22eb7f7b924a676f52e317917b3b5eeceee11c74ef4bc609c0bcec624c166f'
+ sums[packages/pango-0.13.2.0.tar.gz]='4b80c8ed358699738c6956b6ab68a8867de129b521230f5c53daea208923f07c'
+ sums[packages/parsec-3.1.15.0.tar.gz]='98820f4423c0027fc1693bf0fe08b4ef4aabb8eb0a7bf1143561e6b03fd21fed'
+ sums[packages/polyparse-1.12.tar.gz]='f54c63584ace968381de4a06bd7328b6adc3e1a74fd336e18449e0dd7650be15'
+ sums[packages/primitive-0.6.1.1.tar.gz]='f20b8c1efa50fc55a79b5b8c14a1e003ee390f72d796123e5a40e4b88ac50b8f'
+ sums[packages/random-1.1.tar.gz]='b718a41057e25a3a71df693ab0fe2263d492e759679b3c2fea6ea33b171d3a5a'
+ sums[packages/text-1.2.2.2.tar.gz]='31465106360a7d7e214d96f1d1b4303a113ffce1bde44a4e614053a1e5072df9'
+ sums[packages/tf-random-0.5.tar.gz]='2e30cec027b313c9e1794d326635d8fc5f79b6bf6e7580ab4b00186dadc88510'
+ sums[packages/th-compat-0.1.6.tar.gz]='b781a0c059872bc95406d00e98f6fa7d9e81e744730f75186583cb4dcea0a4eb'
+ sums[packages/utf8-string-1.0.2.tar.gz]='ee48deada7600370728c4156cb002441de770d0121ae33a68139a9ed9c19b09a'
+ sums[packages/vector-0.12.0.0.tar.gz]='27bf375d0efbff61acaeb75a2047afcbdac930191069a59da4a474b9bf80ec95'
+
+ # Patches
+ sums[patches/gtk-0.14.5.patch]='d5e0e8041a8109a1039c7a36a1c0dde6ca805f685f504b4894527e36e0ab70c2'
+ # Patch forgtk2hs-buildtools gotten from https://github.com/gtk2hs/gtk2hs/pull/304
+ sums[patches/gtk2hs-buildtools-0.13.5.0.patch]='54b6eeb9842e18a0d77e70f3f6ca884b59a4efef13a915e450bbdf992ac9f034'
+ sums[patches/old-locale-1.0.0.7.patch]='452b1fcfde6364a7ef59d40406fa3117159fe0f2cdd1cedeaabb0efeec066d77'
+
+ for src in "${!sums[@]}"; do
+ path=$ROOT/$src
+ if echo $src | grep -q '^patches/'; then
+ path=$CWD/$src
+ fi
+
+ if [ ! -f $path ]; then
+ echo "ERROR: File $path is missing" >&2
+ exit 1
+ fi
+
+ expected=${sums[$src]}
+ actual=$(sha256sum $path | awk '{ print $1 }')
+ if [ "$actual" != "$expected" ]; then
+ echo "ERROR: File $src has unexpected SHA256 sum (expected $expected, actual $actual)" >&2
+ exit 1
+ fi
+ done
+}
+
+function install_libtinfo5()
+{
+ cd $TMP
+ mkdir libtinfo5
+ cd libtinfo5
+ ar x ../../libtinfo5_6.4-4_amd64.deb
+ tar xvf data.tar.xz
+ mkdir $DEST/libtinfo5
+ mv lib/x86_64-linux-gnu/libtinfo.so.5* $DEST/libtinfo5
+}
+
+function install_ghc()
+{
+ cd $TMP
+ tar xvf ../ghc-8.0.1-x86_64-deb8-linux.tar.xz
+ cd ghc-8.0.1
+ ./configure --prefix=$DEST/ghc-8.0.1
+ make install
+}
+
+function install_package()
+{
+ PRGNAM_VERSION=$1
+
+ cd $TMP
+ tar xvf ../packages/${PRGNAM_VERSION}.tar.gz
+ cd $PRGNAM_VERSION
+
+ # Apply patches if any
+ for p in $(find $CWD/patches -name "${PRGNAM_VERSION}*.patch"); do
+ patch -p1 < $p
+ done
+
+ # An inspiration for installing Cabal packages was taken from Haskell
+ # slackbuilds at https://slackbuilds.org/result/?search=haskell&sv=15.0
+ PREFIX=$DEST_PACKAGES/$PRGNAM_VERSION
+ runghc Setup configure \
+ --prefix=$PREFIX \
+ --libdir=$PREFIX/lib \
+ --libsubdir=ghc-8.0.1/$PRGNAM_VERSION \
+ --enable-shared
+
+ runghc Setup build
+ runghc Setup copy
+ runghc Setup register --gen-pkg-config
+
+ # Some packages don't have libraries (the .conf file), just executables in bin/
+ if [ -f $PRGNAM_VERSION.conf ]; then
+ cp $PRGNAM_VERSION.conf $PACKAGE_DB
+
+ which ghc-pkg
+ ghc-pkg recache
+
+ # Check whether the package has really been registered
+ if [ -z "$(ghc-pkg list | grep "^ *$PRGNAM_VERSION$")" ]; then
+ echo "ERROR: Cannot register package $PRGNAM_VERSION" >&2
+ exit 1
+ fi
+ fi
+}
+
+function install_by_meson()
+{
+ PRGNAM_VERSION=$1
+
+ cd $TMP
+ tar xvf $ROOT/src/$PRGNAM_VERSION.tar.?z
+ cd $PRGNAM_VERSION
+
+ mkdir build
+ cd build
+ meson --prefix=$DEST/$PRGNAM_VERSION \
+ --buildtype=release \
+ ..
+ ninja
+ ninja install
+}
+
+function install_glib()
+{
+ install_by_meson glib-2.60.0
+}
+
+function install_gobject_introspection()
+{
+ install_by_meson gobject-introspection-1.66.1
+}
+
+function install_pango()
+{
+ install_by_meson pango-1.43.0
+}
+
+function install_glade()
+{
+ cd $TMP
+ tar xvf ../src/glade3-3.8.6.tar.xz
+ cd glade3-3.8.6
+
+ ./configure --prefix=$DEST/glade3-3.8.6
+ make
+ make install
+}
+
+function print_env_script()
+{
+ cat <<EOF
+export LD_LIBRARY_PATH="$DEST/libtinfo5:\$LD_LIBRARY_PATH"
+export PATH="$DEST/ghc-8.0.1/bin:\$PATH"
+export PACKAGE_DB="$DEST/ghc-8.0.1/lib/ghc-8.0.1/package.conf.d"
+
+export PATH="$DEST/packages/happy-1.19.5/bin:\$PATH"
+export PATH="$DEST/packages/alex-3.1.7/bin:\$PATH"
+export PATH="$DEST/packages/gtk2hs-buildtools-0.13.5.0/bin/:\$PATH"
+
+export LD_LIBRARY_PATH="$DEST/glib-2.60.0/lib64:\$LD_LIBRARY_PATH"
+export PKG_CONFIG_PATH="$DEST/glib-2.60.0/lib64/pkgconfig:\$PKG_CONFIG_PATH"
+
+export LD_LIBRARY_PATH="$DEST/gobject-introspection-1.66.1/lib64:\$LD_LIBRARY_PATH"
+export PKG_CONFIG_PATH="$DEST/gobject-introspection-1.66.1/lib64/pkgconfig:\$PKG_CONFIG_PATH"
+
+export LD_LIBRARY_PATH="$DEST/pango-1.43.0/lib64:\$LD_LIBRARY_PATH"
+export PKG_CONFIG_PATH="$DEST/pango-1.43.0/lib64/pkgconfig:\$PKG_CONFIG_PATH"
+
+export LD_LIBRARY_PATH="$DEST/glade3-3.8.6/lib64:\$LD_LIBRARY_PATH"
+
+export PATH="$DEST/glade3-3.8.6/bin/:\$PATH"
+
+# Indicate in the prompt that this is a special shell environment
+export PS1="ch23 \$PS1"
+EOF
+}
+
+
+#------------------------------------------------------------------------------
+# MAIN
+#------------------------------------------------------------------------------
+
+if [ ! -f main.sh -o ! -d patches ]; then
+ echo "ERROR: Current directory must be the one containing this script and the patches" >&2
+ exit 1
+fi
+
+if [ ! -d $ROOT ]; then
+ mkdir -p $DEST
+fi
+
+if [ ! -d $DEST ]; then
+ mkdir -p $DEST
+fi
+
+if [ ! -d $TMP ]; then
+ mkdir -p $TMP
+fi
+
+print_env_script >$DEST/env
+source $DEST/env
+
+download_sources
+verify_sources
+
+install_libtinfo5
+install_ghc
+
+install_package random-1.1
+install_package primitive-0.6.1.1
+install_package tf-random-0.5
+install_package QuickCheck-2.8.2
+install_package mtl-2.2.2
+install_package text-1.2.2.2
+install_package polyparse-1.12
+install_package HaXml-1.25.3
+install_package old-locale-1.0.0.7
+install_package old-time-1.1.0.4
+install_package convertible-1.1.1.0
+install_package utf8-string-1.0.2
+install_package HDBC-2.4.0.2
+install_package HDBC-sqlite3-2.3.3.1
+install_package network-2.6.3.1
+install_package parsec-3.1.15.0
+install_package th-compat-0.1.6
+install_package network-uri-2.6.4.2
+install_package HTTP-4000.4.1
+install_package hashable-1.2.4.0
+install_package vector-0.12.0.0
+install_package hashtables-1.2.1.1
+install_package network-2.6.3.1
+install_package alex-3.1.7
+install_package happy-1.19.5
+install_package gtk2hs-buildtools-0.13.5.0
+
+install_glib
+
+install_package glib-0.13.4.1
+install_package cairo-0.13.8.1
+
+install_gobject_introspection
+install_pango
+
+install_package pango-0.13.2.0
+
+install_package gtk-0.14.5
+install_package glade-0.13.1
+
+install_glade
diff --git a/ch23/support/patches/gtk-0.14.5.patch b/ch23/support/patches/gtk-0.14.5.patch
new file mode 100644
index 0000000..baf753a
--- /dev/null
+++ b/ch23/support/patches/gtk-0.14.5.patch
@@ -0,0 +1,24 @@
+diff -rupN gtk-0.14.3/Graphics/UI/Gtk/Embedding/Types.chs new-gtk-0.14.3/Graphics/UI/Gtk/Embedding/Types.chs
+--- gtk-0.14.3/Graphics/UI/Gtk/Embedding/Types.chs 2016-05-22 03:45:58.000000000 +0200
++++ new-gtk-0.14.3/Graphics/UI/Gtk/Embedding/Types.chs 2025-08-01 21:34:55.810556987 +0200
+@@ -45,7 +45,7 @@ import Foreign.ForeignPtr (ForeignPtr, c
+ #if __GLASGOW_HASKELL__ >= 707
+ import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
+ #else
+-import Foreign.ForeignPtr (unsafeForeignPtrToPtr)
++import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
+ #endif
+ import Foreign.C.Types (CULong(..), CUInt(..), CULLong(..))
+ import System.Glib.GType (GType, typeInstanceIsA)
+diff -rupN gtk-0.14.3/Graphics/UI/Gtk/Types.chs new-gtk-0.14.3/Graphics/UI/Gtk/Types.chs
+--- gtk-0.14.3/Graphics/UI/Gtk/Types.chs 2016-05-22 03:45:58.000000000 +0200
++++ new-gtk-0.14.3/Graphics/UI/Gtk/Types.chs 2025-08-01 21:35:08.841037917 +0200
+@@ -772,7 +772,7 @@ import Foreign.ForeignPtr (ForeignPtr, c
+ #if __GLASGOW_HASKELL__ >= 707
+ import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
+ #else
+-import Foreign.ForeignPtr (unsafeForeignPtrToPtr)
++import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
+ #endif
+ import Foreign.C.Types (CULong(..), CUInt(..), CULLong(..))
+ import System.Glib.GType (GType, typeInstanceIsA)
diff --git a/ch23/support/patches/gtk2hs-buildtools-0.13.5.0.patch b/ch23/support/patches/gtk2hs-buildtools-0.13.5.0.patch
new file mode 100644
index 0000000..9052c9d
--- /dev/null
+++ b/ch23/support/patches/gtk2hs-buildtools-0.13.5.0.patch
@@ -0,0 +1,12 @@
+diff -rupN gtk2hs-buildtools-0.13.5.0/c2hs/c/CLexer.x new-gtk2hs-buildtools-0.13.5.0/c2hs/c/CLexer.x
+--- gtk2hs-buildtools-0.13.5.0/c2hs/c/CLexer.x 1970-01-01 01:00:00.000000000 +0100
++++ new-gtk2hs-buildtools-0.13.5.0/c2hs/c/CLexer.x 2025-08-01 14:51:49.676123965 +0200
+@@ -130,7 +130,7 @@ $white+ ;
+ -- * allows further ints after the file name a la GCC; as the GCC CPP docu
+ -- doesn't say how many ints there can be, we allow an unbound number
+ --
+-\#$space*@int$space*(\"($infname|@charesc)*\"$space*)?(@int$space*)*$eol
++\#$space*@digits$space*(\"($infname|@charesc)*\"$space*)?(@int$space*)*$eol
+ { \pos len str -> setPos (adjustPos (take len str) pos) >> lexToken }
+
+ -- #pragma directive (K&R A12.8)
diff --git a/ch23/support/patches/old-locale-1.0.0.7.patch b/ch23/support/patches/old-locale-1.0.0.7.patch
new file mode 100644
index 0000000..e0e418b
--- /dev/null
+++ b/ch23/support/patches/old-locale-1.0.0.7.patch
@@ -0,0 +1,49 @@
+diff -rupN old-locale-1.0.0.7/old-locale.cabal new-old-locale-1.0.0.7/old-locale.cabal
+--- old-locale-1.0.0.7/old-locale.cabal 2014-11-21 11:45:10.000000000 +0100
++++ new-old-locale-1.0.0.7/old-locale.cabal 2025-08-01 17:44:38.946864154 +0200
+@@ -31,5 +31,5 @@ Library
+ exposed-modules:
+ System.Locale
+
+- build-depends: base >= 4.2 && < 4.9
++ build-depends: base >= 4.2 && < 5
+ ghc-options: -Wall
+diff -rupN old-locale-1.0.0.7/old-locale.cabal~ new-old-locale-1.0.0.7/old-locale.cabal~
+--- old-locale-1.0.0.7/old-locale.cabal~ 1970-01-01 01:00:00.000000000 +0100
++++ new-old-locale-1.0.0.7/old-locale.cabal~ 2025-08-01 17:44:27.052920678 +0200
+@@ -0,0 +1,35 @@
++name: old-locale
++version: 1.0.0.7
++-- NOTE: Don't forget to update ./changelog.md
++license: BSD3
++license-file: LICENSE
++maintainer: libraries@haskell.org
++bug-reports: https://github.com/haskell/old-locale/issues
++synopsis: locale library
++category: System
++build-type: Simple
++Cabal-Version:>=1.10
++tested-with: GHC==7.8.3, GHC==7.8.2, GHC==7.8.1, GHC==7.6.3, GHC==7.6.2, GHC==7.6.1, GHC==7.4.2, GHC==7.4.1, GHC==7.2.2, GHC==7.2.1, GHC==7.0.4, GHC==7.0.3, GHC==7.0.2, GHC==7.0.1, GHC==6.12.3
++description:
++ This package provides the ability to adapt to
++ locale conventions such as date and time formats.
++
++extra-source-files:
++ changelog.md
++
++source-repository head
++ type: git
++ location: https://github.com/haskell/old-locale.git
++
++Library
++ default-language: Haskell98
++ other-extensions: CPP
++ if impl(ghc>=7.2)
++ -- && base>=4.4.1
++ other-extensions: Safe
++
++ exposed-modules:
++ System.Locale
++
++ build-depends: base >= 4.2 && < 4.9
++ ghc-options: -Wall