{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreibg6tydndtefz3p5axklnbtaglic6tcqwqifdd2xqet4uvvetdodi",
"uri": "at://did:plc:pi6woz4d47bkuws673w2il2r/app.bsky.feed.post/3mjjmrvrm2ni2"
},
"path": "/t/why-we-built-a-haskell-package-manager-in-rust/13933#post_9",
"publishedAt": "2026-04-15T09:29:28.000Z",
"site": "https://discourse.haskell.org",
"tags": [
"documentation",
"hx/crates/hx-cli/src/commands/init.rs at b9dcac765e9f500e1bce398e6a395a012cb2dae6 · arcanist-sh/hx · GitHub"
],
"textContent": "BurningWitness:\n\n> Oh, also there’s a “try it” `curl` at the end of the article that could well be a malicious link.\n\nI read the shell code. It’s not malicious, just a bit sloppy:\n\n\n # Create temp directory\n TMPDIR=$(mktemp -d)\n trap \"rm -rf $TMPDIR\" EXIT\n\n\n$TMPDIR is unquoted. In this case that cannot lead to arbitrary file deletion, but it’s still bad form.\n\n\n get_install_dir() {\n if [ -n \"$HX_INSTALL_DIR\" ]; then\n echo \"$HX_INSTALL_DIR\"\n elif [ -d \"$HOME/.local/bin\" ]; then\n echo \"$HOME/.local/bin\"\n elif [ -w \"/usr/local/bin\" ]; then\n echo \"/usr/local/bin\"\n else\n echo \"$HOME/.local/bin\"\n fi\n }\n\n\nIt may install into `/usr/local/bin` if it’s writable. Weirdly enough, this code actually prefers a global installation, unless `\"$HOME/.local/bin\"` already exists… it doesn’t even try to create it.\n\nAnd then it also asks for sudo:\n\n\n if [ -w \"$INSTALL_DIR\" ]; then\n cp \"$BINARY\" \"$INSTALL_DIR/\"\n chmod +x \"$INSTALL_DIR/hx\"\n else\n info \"Requesting sudo access...\"\n sudo mkdir -p \"$INSTALL_DIR\"\n sudo cp \"$BINARY\" \"$INSTALL_DIR/\"\n sudo chmod +x \"$INSTALL_DIR/hx\"\n fi\n\n\n* * *\n\nDespite that, I tried it in a docker container and I found the UX lacking:\n\n\n $ hx new my-app\n error: unrecognized subcommand 'my-app'\n\n Usage: hx new [OPTIONS] <COMMAND>\n\n For more information, try '--help'.\n\n\nWell… ok. This is what the documentation told me to run. I guess I can do it myself:\n\n\n $ mkdir foo\n $ cd foo\n $ hx init\n ✓ Created bin project: foo\n Next steps:\n cd /root/foo\n hx build\n hx run\n $ hx build\n Unable to create steel home directory \"/root/.local/share/steel\": No such file or directory (os error 2)\n Building foo\n\n error: toolchain not found: cabal\n\n fix: Run `hx toolchain install`\n Install cabal\n $ hx toolchain install\n warning: No version specified\n Example: hx toolchain install 9.8.2\n Or: hx toolchain install --ghc 9.8.2\n Or: hx toolchain install --bhc latest\n $ hx toolchain install --ghc latest\n Installing GHC latest\n WARN Direct GHC installation failed, falling back to ghcup: configuration error: Invalid GHC version format: latest. Expected format like 9.8.2\n\n error: toolchain not found: ghcup\n\n fix: Run `hx toolchain install`\n Install ghcup\n $ hx toolchain install --ghc 9.12.4\n Installing GHC 9.12.4\n ✗ Failed to download GHC 9.12.4 WARN Direct GHC installation failed, falling back to ghcup: configuration error: GHC 9.12.4 download failed: HTTP 404 Not Found. This version may not be available for your platform.\n\n error: toolchain not found: ghcup\n\n fix: Run `hx toolchain install`\n Install ghcup\n\n\nI don’t know. I ran like 4 commands and they all failed. It tells me to run `hx toolchain install`, but that isn’t a valid command even (lacks a version). Then the examples indicate I can use `latest` as version, but that seems to fail as well. Then it seems downloading GHC is broken (it has its own installation logic), then it tries to fall back to ghcup, but that isn’t installed either and `hx` can’t install ghcup.\n\nLuckily, I know a thing or two about ghcup, so I manage to install it after all.\n\nAfter installing the toolchain, I run:\n\n\n $ hx build\n Unable to create steel home directory \"/root/.local/share/steel\": No such file or directory (os error 2)\n Building my-app\n ✗ Build failed Warning: The package list for 'hackage.haskell.org' does not exist. Run 'cabal\n update' to download it.\n Error: [Cabal-7107]\n Could not resolve dependencies:\n [__0] trying: my-app-0.1.0.0 (user goal)\n [__1] next goal: base (dependency of my-app)\n [__1] rejecting: base-4.22.0.0/installed-fde1 (conflict: my-app => base^>=4.17 || ^>=4.18 || ^>=4.19 || ^>=4.20)\n [__1] fail (backjumping, conflict set: base, my-app)\n After searching the rest of the dependency tree exhaustively, these were the goals I've had most trouble fulfilling: my-app, base\n\n\n\n error: build failed\n\n\nOh… uhm. What is `cabal`? I thought I only have to use `hx`. Anyway.\n\n\n $ hx build\n Unable to create steel home directory \"/root/.local/share/steel\": No such file or directory (os error 2)\n Building my-app\n ✗ Build failed Error: [Cabal-7107]\n Could not resolve dependencies:\n [__0] trying: my-app-0.1.0.0 (user goal)\n [__1] next goal: base (dependency of my-app)\n [__1] rejecting: base-4.22.0.0/installed-fde1 (conflict: my-app => base^>=4.17 || ^>=4.18 || ^>=4.19 || ^>=4.20)\n [__1] skipping: base; 4.22.0.0, 4.21.2.0, 4.21.1.0, 4.21.0.0 (has the same characteristics that caused the previous version to fail: excluded by constraint '^>=4.17 || ^>=4.18 || ^>=4.19 || ^>=4.20' from 'my-app')\n [__1] rejecting: base; 4.20.2.0, 4.20.1.0, 4.20.0.1, 4.20.0.0, 4.19.2.0, 4.19.1.0, 4.19.0.0, 4.18.3.0, 4.18.2.1, 4.18.2.0, 4.18.1.0, 4.18.0.0, 4.17.2.1, 4.17.2.0, 4.17.1.0, 4.17.0.0, 4.16.4.0, 4.16.3.0, 4.16.2.0, 4.16.1.0, 4.16.0.0, 4.15.1.0, 4.15.0.0, 4.14.3.0, 4.14.2.0, 4.14.1.0, 4.14.0.0, 4.13.0.0, 4.12.0.0, 4.11.1.0, 4.11.0.0, 4.10.1.0, 4.10.0.0, 4.9.1.0, 4.9.0.0, 4.8.2.0, 4.8.1.0, 4.8.0.0, 4.7.0.2, 4.7.0.1, 4.7.0.0, 4.6.0.1, 4.6.0.0, 4.5.1.0, 4.5.0.0, 4.4.1.0, 4.4.0.0, 4.3.1.0, 4.3.0.0, 4.2.0.2, 4.2.0.1, 4.2.0.0, 4.1.0.0, 4.0.0.0, 3.0.3.2, 3.0.3.1 (constraint from non-reinstallable package requires installed instance)\n [__1] fail (backjumping, conflict set: base, my-app)\n After searching the rest of the dependency tree exhaustively, these were the goals I've had most trouble fulfilling: base, my-app\n\n\n\n error: build failed\n\n\nAha, so it created the project with some random bounds. I don’t even know what it used.\n\nI looked up the code that generates the project: hx/crates/hx-cli/src/commands/init.rs at b9dcac765e9f500e1bce398e6a395a012cb2dae6 · arcanist-sh/hx · GitHub\n\n\n fn generate_cabal(name: &str, kind: ProjectKind) -> String {\n match kind {\n ProjectKind::Bin => format!(\n r#\"cabal-version: 3.0\n name: {name}\n version: 0.1.0.0\n synopsis: A Haskell project\n license: MIT\n author: Author\n maintainer: author@example.com\n build-type: Simple\n\n executable {name}\n main-is: Main.hs\n hs-source-dirs: src\n default-language: GHC2021\n build-depends:\n base ^>=4.17 || ^>=4.18 || ^>=4.19 || ^>=4.20\n ghc-options: -Wall\n \"#\n ),\n ProjectKind::Lib => format!(\n r#\"cabal-version: 3.0\n name: {name}\n version: 0.1.0.0\n synopsis: A Haskell library\n license: MIT\n author: Author\n maintainer: author@example.com\n build-type: Simple\n\n library\n exposed-modules: Lib\n hs-source-dirs: src\n default-language: GHC2021\n build-depends:\n base ^>=4.17 || ^>=4.18 || ^>=4.19 || ^>=4.20\n ghc-options: -Wall\n \"#\n ),\n }\n }\n\n\nAnd at this point, I’m not quite sure if I want to continue my experimentation.",
"title": "Why We Built a Haskell Package Manager in Rust"
}