Why We Built a Haskell Package Manager in Rust
BurningWitness:
Oh, also there’s a “try it”
curlat the end of the article that could well be a malicious link.
I read the shell code. It’s not malicious, just a bit sloppy:
# Create temp directory
TMPDIR=$(mktemp -d)
trap "rm -rf $TMPDIR" EXIT
$TMPDIR is unquoted. In this case that cannot lead to arbitrary file deletion, but it’s still bad form.
get_install_dir() {
if [ -n "$HX_INSTALL_DIR" ]; then
echo "$HX_INSTALL_DIR"
elif [ -d "$HOME/.local/bin" ]; then
echo "$HOME/.local/bin"
elif [ -w "/usr/local/bin" ]; then
echo "/usr/local/bin"
else
echo "$HOME/.local/bin"
fi
}
It 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.
And then it also asks for sudo:
if [ -w "$INSTALL_DIR" ]; then
cp "$BINARY" "$INSTALL_DIR/"
chmod +x "$INSTALL_DIR/hx"
else
info "Requesting sudo access..."
sudo mkdir -p "$INSTALL_DIR"
sudo cp "$BINARY" "$INSTALL_DIR/"
sudo chmod +x "$INSTALL_DIR/hx"
fi
Despite that, I tried it in a docker container and I found the UX lacking:
$ hx new my-app
error: unrecognized subcommand 'my-app'
Usage: hx new [OPTIONS] <COMMAND>
For more information, try '--help'.
Well… ok. This is what the documentation told me to run. I guess I can do it myself:
$ mkdir foo
$ cd foo
$ hx init
✓ Created bin project: foo
Next steps:
cd /root/foo
hx build
hx run
$ hx build
Unable to create steel home directory "/root/.local/share/steel": No such file or directory (os error 2)
Building foo
error: toolchain not found: cabal
fix: Run `hx toolchain install`
Install cabal
$ hx toolchain install
warning: No version specified
Example: hx toolchain install 9.8.2
Or: hx toolchain install --ghc 9.8.2
Or: hx toolchain install --bhc latest
$ hx toolchain install --ghc latest
Installing GHC latest
WARN Direct GHC installation failed, falling back to ghcup: configuration error: Invalid GHC version format: latest. Expected format like 9.8.2
error: toolchain not found: ghcup
fix: Run `hx toolchain install`
Install ghcup
$ hx toolchain install --ghc 9.12.4
Installing GHC 9.12.4
✗ 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.
error: toolchain not found: ghcup
fix: Run `hx toolchain install`
Install ghcup
I 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.
Luckily, I know a thing or two about ghcup, so I manage to install it after all.
After installing the toolchain, I run:
$ hx build
Unable to create steel home directory "/root/.local/share/steel": No such file or directory (os error 2)
Building my-app
✗ Build failed Warning: The package list for 'hackage.haskell.org' does not exist. Run 'cabal
update' to download it.
Error: [Cabal-7107]
Could not resolve dependencies:
[__0] trying: my-app-0.1.0.0 (user goal)
[__1] next goal: base (dependency of my-app)
[__1] rejecting: base-4.22.0.0/installed-fde1 (conflict: my-app => base^>=4.17 || ^>=4.18 || ^>=4.19 || ^>=4.20)
[__1] fail (backjumping, conflict set: base, my-app)
After searching the rest of the dependency tree exhaustively, these were the goals I've had most trouble fulfilling: my-app, base
error: build failed
Oh… uhm. What is cabal? I thought I only have to use hx. Anyway.
$ hx build
Unable to create steel home directory "/root/.local/share/steel": No such file or directory (os error 2)
Building my-app
✗ Build failed Error: [Cabal-7107]
Could not resolve dependencies:
[__0] trying: my-app-0.1.0.0 (user goal)
[__1] next goal: base (dependency of my-app)
[__1] rejecting: base-4.22.0.0/installed-fde1 (conflict: my-app => base^>=4.17 || ^>=4.18 || ^>=4.19 || ^>=4.20)
[__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')
[__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)
[__1] fail (backjumping, conflict set: base, my-app)
After searching the rest of the dependency tree exhaustively, these were the goals I've had most trouble fulfilling: base, my-app
error: build failed
Aha, so it created the project with some random bounds. I don’t even know what it used.
I looked up the code that generates the project: hx/crates/hx-cli/src/commands/init.rs at b9dcac765e9f500e1bce398e6a395a012cb2dae6 · arcanist-sh/hx · GitHub
fn generate_cabal(name: &str, kind: ProjectKind) -> String {
match kind {
ProjectKind::Bin => format!(
r#"cabal-version: 3.0
name: {name}
version: 0.1.0.0
synopsis: A Haskell project
license: MIT
author: Author
maintainer: author@example.com
build-type: Simple
executable {name}
main-is: Main.hs
hs-source-dirs: src
default-language: GHC2021
build-depends:
base ^>=4.17 || ^>=4.18 || ^>=4.19 || ^>=4.20
ghc-options: -Wall
"#
),
ProjectKind::Lib => format!(
r#"cabal-version: 3.0
name: {name}
version: 0.1.0.0
synopsis: A Haskell library
license: MIT
author: Author
maintainer: author@example.com
build-type: Simple
library
exposed-modules: Lib
hs-source-dirs: src
default-language: GHC2021
build-depends:
base ^>=4.17 || ^>=4.18 || ^>=4.19 || ^>=4.20
ghc-options: -Wall
"#
),
}
}
And at this point, I’m not quite sure if I want to continue my experimentation.
Discussion in the ATmosphere