Lockfiles

hx uses lockfiles to ensure reproducible builds across machines and over time.

Why Lockfiles?

The Problem

Without lockfiles:

  • hx build on Monday might use aeson-2.1.0
  • hx build on Tuesday might use aeson-2.2.0 (newly released)
  • Builds break unexpectedly
  • CI builds differ from local builds

The Solution

With lockfiles:

  • All dependencies are pinned to exact versions
  • Builds are reproducible indefinitely
  • Updates are explicit and intentional

Creating a Lockfile

Generate hx.lock:

hx lock

This:

  1. Reads dependencies from your .cabal file
  2. Resolves all transitive dependencies
  3. Pins to specific versions
  4. Records checksums for verification
  5. Writes hx.lock

Lockfile Contents

hx.lock contains:

# Auto-generated by hx. Do not edit manually.

version = 1
generated = "2024-01-15T10:30:00Z"
hx_version = "0.4.0"

[toolchain]
ghc = "9.8.2"
cabal = "3.10.3.0"

[index]
hackage = "2024-01-15T00:00:00Z"

[[package]]
name = "aeson"
version = "2.1.2.1"
sha256 = "abc123..."
source = "hackage"
dependencies = ["base", "text", "containers"]

[[package]]
name = "text"
version = "2.0.2"
sha256 = "def456..."
source = "hackage"
dependencies = ["base", "bytestring"]

Using the Lockfile

Sync Dependencies

Install the locked versions:

hx sync

Build with Locked Versions

Regular build commands use the lockfile automatically:

hx build  # Uses versions from hx.lock

Updating Dependencies

Update All

hx lock --update

Or:

hx update
hx lock

Update Specific Package

hx update aeson
hx lock

Check for Updates

hx outdated

Lockfile Workflow

New Project

hx new my-project
cd my-project
hx lock         # Create initial lockfile
git add hx.lock
git commit -m "Initial lockfile"

Adding Dependencies

hx add text containers
hx lock         # Update lockfile
git add hx.lock *.cabal
git commit -m "Add text and containers"

Updating Dependencies

hx outdated     # See what's outdated
hx update       # Update to latest compatible
hx test         # Verify everything works
git add hx.lock
git commit -m "Update dependencies"

Cloning a Project

git clone https://github.com/example/project
cd project
hx sync         # Install locked versions
hx build

Version Control

Commit hx.lock

Always commit hx.lock:

git add hx.lock
git commit -m "Update dependencies"

Never Gitignore

Do NOT add to .gitignore:

# Bad - don't do this!
hx.lock

Merge Conflicts

If conflicts occur:

# Accept one version
git checkout --ours hx.lock

# Regenerate
hx lock

# Commit
git add hx.lock
git commit

CI Integration

Verify Lockfile

Ensure lockfile is up-to-date:

- name: Verify lockfile
  run: hx lock --check

Frozen Builds

Fail if lockfile would change:

- name: Build (frozen)
  run: |
    hx lock --frozen
    hx build

Complete CI Example

name: CI

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup hx
        run: curl -fsSL https://get.arcanist.sh/hx | sh

      - name: Verify lockfile
        run: hx lock --frozen

      - name: Sync dependencies
        run: hx sync

      - name: Build
        run: hx build

      - name: Test
        run: hx test

Lockfile Strategies

Configure in hx.toml:

[dependencies]
lock-strategy = "hx"  # or "cabal-freeze"

hx (Default)

Uses hx’s TOML lockfile format.

Advantages:

  • Human-readable
  • Includes checksums
  • Toolchain pinning
  • Better merge conflict handling

cabal-freeze

Uses cabal.project.freeze for compatibility.

Advantages:

  • Works with plain Cabal
  • No hx dependency in CI
  • Familiar to Cabal users

Index State

Pin the Hackage index state for extra reproducibility:

[dependencies]
index-state = "2024-01-15T00:00:00Z"

This ensures the same packages are available even if newer ones are uploaded.

Offline Builds

With a lockfile, build offline:

# First, download all dependencies
hx sync

# Later, build offline
hx build --offline

Troubleshooting

Lockfile Out of Sync

error: hx.lock is out of sync with hx.toml

fix: Run `hx lock` to update

Missing Lockfile

warning: No hx.lock found. Builds may not be reproducible.

hint: Run `hx lock` to create a lockfile

Checksum Mismatch

error: Package 'aeson-2.1.2.1' checksum mismatch
  expected: abc123...
  got: def456...

This may indicate:
  - Corrupted download
  - Package replaced on Hackage (rare)

fix: Run `hx lock --force` to regenerate

Best Practices

  1. Always commit hx.lock
  2. Run hx lock --check in CI
  3. Review lockfile changes in PRs
  4. Update dependencies regularly
  5. Use hx sync when cloning

See Also