Skip to content

Instantly share code, notes, and snippets.

@jasagredo
Last active August 16, 2024 08:36
Show Gist options
  • Save jasagredo/84da6d24c70aa4113ed64df3e6869269 to your computer and use it in GitHub Desktop.
Save jasagredo/84da6d24c70aa4113ed64df3e6869269 to your computer and use it in GitHub Desktop.
Haskell ❤️ MSYS2

Haskell + Windows (MSYS2)

This document describes my setup for working with Haskell on Windows. Notice I don't know much of the intricacies of Windows but it seems I was able to make it work.

It will contain also personal recommendations.


Environment

Git

Git

Install git using scoop in PowerShell:

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression
scoop install git

Notice we are installing Git for Windows with this. It seems like it has worked properly for me until now. Some configs:

➜ cat .gitconfig
[user]
  name = ...
  email = ...
[core]
  autocrlf = input
  sshCommand = C:/Windows/System32/OpenSSH/ssh.exe
[gc]
  auto = 256
[alias]
  from-linux = "!__git_rm_symlinks() {\n  case \"$1\" in (-h)\n    printf 'usage: git from-linux [symlink] [symlink] [...]\\n'\n    return 0\n  esac\n  ppid=$$\n  case $# in\n    (0) git ls-files -s | grep -E '^120000' | cut -f2 ;;\n    (*) printf '%s\\n' \"$@\" ;;\n  esac | while IFS= read -r symlink; do\n    case \"$symlink\" in\n      (*/*) symdir=${symlink%/*} ;;\n      (*) symdir=. ;;\n    esac\n    git checkout -- \"$symlink\"\n    src=\"${symdir}/$(cat \"$symlink\")\"\n    posix_to_dos_sed='s_^/\\([A-Za-z]\\)_\\1:_;s_/_\\\\\\\\_g'\n    doslnk=$(printf '%s\\n' \"$symlink\" | sed \"$posix_to_dos_sed\")\n    dossrc=$(printf '%s\\n' \"$src\" | sed \"$posix_to_dos_sed\")\n    if [ -f \"$src\" ]; then\n      rm -f \"$symlink\"\n      cmd //C mklink //H \"$doslnk\" \"$dossrc\"\n    elif [ -d \"$src\" ]; then\n      rm -f \"$symlink\"\n      cmd //C mklink //J \"$doslnk\" \"$dossrc\"\n    else\n      printf 'error: git-rm-symlink: Not a valid source\\n' >&2\n      printf '%s =/=> %s  (%s =/=> %s)...\\n' \\\n          \"$symlink\" \"$src\" \"$doslnk\" \"$dossrc\" >&2\n      false\n    fi || printf 'ESC[%d]: %d\\n' \"$ppid\" \"$?\"\n    git update-index --assume-unchanged \"$symlink\"\n  done | awk '\n    BEGIN { status_code = 0 }\n    /^ESC\\['\"$ppid\"'\\]: / { status_code = $2 ; next }\n    { print }\n    END { exit status_code }\n  '\n}\n__git_rm_symlinks"
  to-linux = "!__git_checkout_symlinks() {\n  case \"$1\" in (-h)\n    printf 'usage: git to-linux [symlink] [symlink] [...]\\n'\n    return 0\n  esac\n  case $# in\n    (0) git ls-files -s | grep -E '^120000' | cut -f2 ;;\n    (*) printf '%s\\n' \"$@\" ;;\n  esac | while IFS= read -r symlink; do\n    git update-index --no-assume-unchanged \"$symlink\"\n    rmdir \"$symlink\" >/dev/null 2>&1\n    git checkout -- \"$symlink\"\n    printf 'Restored git symlink: %s -> %s\\n' \"$symlink\" \"$(cat \"$symlink\")\"\n  done\n}\n__git_checkout_symlinks"
[gpg]
  program = C:/Program Files (x86)/GnuPG/bin/gpg.exe
[commit]
  gpgsign = true
[core]
  editor = nano
  symlinks = true

Use git from-linux and git to-linux to remove and restore symbolic links.

For gpg, I use Gpg4win, and for ssh I use Window's OpenSSH

MSYS2

MSYS2

The developer environment will be based on MSYS2. Download the installer from their webpage and follow the wizard steps.

System upgrade

Run pacman -Syuu twice, to update the runtime and to update the packages.

Setting the HOME for MSYS2

Add this line to your /etc/nsswitch.conf file from inside MSYS2.

db_home: <whatever-was-here> windows

and restart the terminal.

Choosing an environment

GHC ships a minimal toolchain in order to not depend on the user's toolchain (currently 0.8 here, check here for the version on your particular GHC).

Before GHC 9.4, the base environment was GCC-based, thus the best way to align things was to use a MINGW64 environment.

After GHC 9.4, the base environment is clang-based. So I would suggest using the CLANG64 environment.

Inheriting the Windows' PATH

Add the argument -use-full-path to the msys2_shell.cmd (right-click on the icon that appears when you search for MSYS2 in your start menu, and modify the shortcut).

Essential packages (🎨)

Some packages that I find very useful to have around:

  • mingw-w64-clang-x86_64-pkgconf: there is a msys2/pkgconf package but it doesn't understand Windows paths in PKG_CONFIG_PATH... Sadly this means that if you change environments you will have to switch the pkg-config package (or install a new one).
  • base-devel: generally useful
  • mingw-w64-clang-x86_64-zlib
  • mingw-w64-clang-x86_64-fd: useful replace for find.
  • mingw-w64-clang-x86_64-jq
  • mingw-w64-clang-x86_64-emacs: just in case.
  • mingw-w64-clang-x86_64-openssl
  • mingw-w64-clang-x86_64-python and mingw-w64-clang-x86_64-python-pip.

or in one line:

pacman --noconfirm -S base-devel mingw-w64-clang-x86_64-pkgconf mingw-w64-clang-x86_64-zlib mingw-w64-clang-x86_64-fd mingw-w64-clang-x86_64-jq mingw-w64-clang-x86_64-emacs mingw-w64-clang-x86_64-python mingw-w64-clang-x86_64-python-pip

Changing the default shell (🎨)

I tend to work with zsh so I would suggest installing pacman -S zsh.

Windows Terminal

Windows Terminal

I would suggest installing the Windows Terminal and FiraCode Nerd font (make sure to install it for all users or Windows Terminal will complain). There you can configure the different profiles you want to use. My settings are as follows (notice the -use-full-path and the -shell zsh):

"defaultProfile": "<some profile guid from below>",
"profiles":
 {
   "defaults": {},
   "list":
   [
      {
          "colorScheme": "One Half Dark",
          "commandline": "C:\\msys64\\msys2_shell.cmd -defterm -here -no-start -clang64 -shell zsh -use-full-path",
          "font": 
          {
              "face": "FiraCode Nerd Font"
          },
          "guid": "...",
          "hidden": false,
          "icon": "C:\\msys64\\clang64.ico",
          "name": "MSYS2 / CLANG64",
          "startingDirectory": "C:/Users/Javier"
      },
      {
          "colorScheme": "Solarized Dark",
          "commandline": "C:\\Program Files\\PowerShell\\7\\pwsh.exe",
          "font": 
          {
              "face": "FiraCode Nerd Font"
          },
          "guid": "...",
          "hidden": false,
          "name": "Windows PowerShell"
      }
  ]
},
"schemes":
[
          {
            "background": "#282C34",
            "black": "#282C34",
            "blue": "#61AFEF",
            "brightBlack": "#5A6374",
            "brightBlue": "#61AFEF",
            "brightCyan": "#56B6C2",
            "brightGreen": "#98C379",
            "brightPurple": "#C678DD",
            "brightRed": "#E06C75",
            "brightWhite": "#DCDFE4",
            "brightYellow": "#E5C07B",
            "cursorColor": "#FFFFFF",
            "cyan": "#56B6C2",
            "foreground": "#DCDFE4",
            "green": "#98C379",
            "name": "One Half Dark",
            "purple": "#C678DD",
            "red": "#E06C75",
            "selectionBackground": "#FFFFFF",
            "white": "#DCDFE4",
            "yellow": "#E5C07B"
        },
        {
            "background": "#002B36",
            "black": "#002B36",
            "blue": "#268BD2",
            "brightBlack": "#073642",
            "brightBlue": "#839496",
            "brightCyan": "#93A1A1",
            "brightGreen": "#586E75",
            "brightPurple": "#6C71C4",
            "brightRed": "#CB4B16",
            "brightWhite": "#FDF6E3",
            "brightYellow": "#657B83",
            "cursorColor": "#FFFFFF",
            "cyan": "#2AA198",
            "foreground": "#839496",
            "green": "#859900",
            "name": "Solarized Dark",
            "purple": "#D33682",
            "red": "#DC322F",
            "selectionBackground": "#FFFFFF",
            "white": "#EEE8D5",
            "yellow": "#B58900"
           }        
]

I also use starship and install it from pacman with mingw-w64-clang-x86_64-starship.

PowerShell

PowerShell logo

Install the latest PowerShell with winget:

winget install --id Microsoft.WindowsTerminal --source winget

Point your Windows Terminal profile to C:\Program Files\PowerShell\7\pwsh.exe.

Add this to your profile (~/Documents/PowerShell/Microsoft.PowerShell_profile.ps1) to enable which and also movement like emacs on the command line:

New-Alias which get-command
Import-Module PSReadLine
Set-PSReadLineOption -EditMode Emacs

If you want starship, then you can install it with winget:

winget install --id Starship.Starship

and then add this to your profile:

Invoke-Expression (&starship init powershell)

Haskell

GHCup

image

Set the following user variables:

Var Value
GHCUP_INSTALL_BASE_PREFIX C:\
GHCUP_MSYS2 C:\msys64
Path Add C:\ghcup\bin and C:\Users\Javier\AppData\Roaming\cabal\bin

Then go to GHCup and get the POSIX install command:

curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh 

And run it in an MSYS2 shell. Restart the terminal and run ghcup tui to select the latest versions of cabal, stack and the desired ghc.

Integrate with cabal

GHCup will have modified your cabal global config like this:

extra-include-dirs: C:\msys64\clang64\include
extra-lib-dirs: C:\msys64\clang64\lib
extra-prog-path: C:\ghcup\bin,
                 C:\Users\<USER>\AppData\Roaming\cabal\bin,
                 C:\msys64\clang64\bin,
                 C:\msys64\usr\bin

This might lead to problems sometimes, see this.

I would say leave it if using CLANG64 and GHC >= 9.4, but if using MINGW64 you might need to comment the include and lib dirs.

I use this function to switch environments if needed:

cabal-msys2-env () {
  sed -i "s/\(ucrt\|mingw\|clang\)/$1/g" $(cygpath -u "$(cabal --help | tail -n1 | sed 's_\r__g' | tr -d ' ')")
}

NOTE: while this is fixed, I suggest adding the following to your cabal config

program-default-options
  ghc-options: -optc-Wno-pragma-pack -optc-Wno-macro-redefined -optc-Wno-missing-declarations

VSCode integration

VSCode

Install VSCode from its webpage and install the Haskell extension and the Haskell syntax extension.

Now configure the settings.json as follows:

{
    "terminal.integrated.profiles.windows": {

...
        "MSYS2 CLANG64": {
            "path": "C:\\msys64\\msys2_shell.cmd",
            "args": [
                "-defterm", "-here", "-no-start", "-clang64", "-shell", "zsh", "-use-full-path"
            ]
        }
    },
...
    "terminal.integrated.defaultProfile.windows": "MSYS2 CLANG64",
...
    "haskell.serverEnvironment": {
          "PATH": "C:/msys64/clang64/bin;C:/msys64/usr/local/bin;C:/msys64/usr/bin;$PATH"
        , "PKG_CONFIG_PATH": "C:/msys64/clang64/lib/pkgconfig;C:/msys64/clang64/share/pkgconfig"
        , "LD_LIBRARY_PATH": "$LD_LIBRARY_PATH"
        , "XDG_DATA_DIRS": "C:/msys64/clang64/share;C:/msys64/usr/share"
        , "PKG_CONFIG_SYSTEM_INCLUDE_PATH": "C:/msys64/clang64/include"
        , "PKG_CONFIG_SYSTEM_LIBRARY_PATH": "C:/msys64/clang64/lib"
        , "LANG": "en_US.UTF-8"
        , "MINGW_PREFIX": "C:/msys64/clang64"
        , "WD": "C:/msys64/usr/bin/"
    },
}

Profit!

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment