Skip to content

Instantly share code, notes, and snippets.

@igor-ramazanov
Last active September 2, 2025 13:41
Show Gist options
  • Select an option

  • Save igor-ramazanov/7bd29ac9be57e14b9672204dba0e3075 to your computer and use it in GitHub Desktop.

Select an option

Save igor-ramazanov/7bd29ac9be57e14b9672204dba0e3075 to your computer and use it in GitHub Desktop.
Self-contained Haskell scripts with Nix optionally compiled as a static binary (using nix bundlers)
#!/usr/bin/env -S nu --login --interactive
# Can be written in any language.
# The last positional argument will be automatically substituted to an absolute path to the script file.
def tasks [] {
{
options: {
case_sensitive: false
completion_algorithm: substring
sort: true
}
completions: [
"build",
"bundle",
"shell",
"run",
]
}
}
export def main [
--compiler: string
--deps: list<string>
--task: string@tasks
--debug
file:path
] {
def log [msg: string] { if $debug { print $msg } }
log $"compiler: ($compiler)"
log $"deps: ($deps)"
log $"task: ($task)"
let basename = $file | path basename
let name = $basename | str replace '.hs' ''
let expr = $"
let
pkgs = import <nixpkgs> {};
dependencies = hpkgs: builtins.map \(dep: hpkgs.${dep}\) ($deps | to json --raw | str replace ',' ' ' --all);
ghc = pkgs.haskell.packages.($compiler).ghcWithPackages dependencies;
source = pkgs.writeText \"($basename)\" ''
(open --raw Scotty.hs | lines | skip while {|line| ($line | str starts-with '#') or ($line | is-empty )} | str join "\n")
'';
name = \"($name)\";
in
pkgs.runCommandNoCCLocal \"($name)\" {pname = \"($name)\"; meta.mainProgram = \"($name)\"; nativeBuildInputs = [ghc];} ''
mkdir -p ./.tmp $out/bin
ghc \\
-outputdir ./.tmp/ \\
-hidir ./.tmp/ \\
-odir ./.tmp/ \\
-dumpdir ./.tmp/ \\
-tmpdir ./.tmp/ \\
-o ./.tmp/($name) \\
--make ${source}
mv ./.tmp/($name) $out/bin/
rm -r ./.tmp/
''
"
match $task {
"build" => {
let command = ["nix", "build", "--out-link", $name, "--impure", "--expr", $expr] | flatten
log ($command | str join ' ')
run-external ...($command)
},
"bundle" => {
let command = ["nix", "bundle", "--bundler", "github:DavHau/nix-portable/v012#zstd-max", "--out-link", ($name + "-bundled"), "--impure", "--expr", $expr] | flatten
log ($command | str join ' ')
run-external ...($command)
},
"shell" => {
# You can implement it the same manner as `build` and `bundle`.
error make {msg: "'shell': not implemented"}
},
"run" => {
# You can implement it the same manner as `build` and `bundle`.
error make {msg: "'run': not implemented"}
}
}
}
#!./haskell.nu --compiler ghc984 --deps [base scotty] --task bundle --debug
{-# LANGUAGE OverloadedStrings #-}
import Web.Scotty
import Data.Monoid (mconcat)
main = scotty 3000 $ do
get "/:word" $ do
beam <- param "word"
html $ mconcat ["<h1>Scotty, ", beam, " me up!</h1>"]
@igor-ramazanov
Copy link
Author

igor-ramazanov commented Aug 1, 2025

Would be cool to rewrite the haskell.nu to use https://github.com/nh2/static-haskell-nix for building static binaries instead of https://github.com/DavHau/nix-portable/tree/main

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