Lockfile Reference

Understanding hx.lock and reproducible builds.

Overview

hx.lock is an auto-generated file that pins all dependencies to exact versions, ensuring reproducible builds across machines and over time.

Purpose

Without a lockfile:

  • Different machines may get different dependency versions
  • Builds may break when new package versions are released
  • CI results may differ from local builds

With a lockfile:

  • Everyone gets the exact same dependency versions
  • Builds are reproducible indefinitely
  • Updates are explicit and intentional

File Format

hx.lock is a TOML file:

# hx.lock
# 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 = "abc123def456..."
source = "hackage"
dependencies = ["base", "text", "containers", "vector", "bytestring"]

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

[[package]]
name = "custom-package"
version = "1.0.0"
source = "git"
git = "https://github.com/user/package"
commit = "abc123def456789..."

Sections

version

Lockfile format version:

version = 1

generated

Timestamp when the lockfile was generated:

generated = "2024-01-15T10:30:00Z"

[toolchain]

Pinned toolchain versions:

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

[index]

Hackage index state:

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

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

[[package]]

Each dependency entry:

[[package]]
name = "aeson"                  # Package name
version = "2.1.2.1"             # Exact version
sha256 = "abc123..."            # Content hash
source = "hackage"              # Package source
dependencies = ["base", "text"] # Direct dependencies

Package Sources

Hackage

[[package]]
name = "aeson"
version = "2.1.2.1"
sha256 = "abc123..."
source = "hackage"

Git Repository

[[package]]
name = "custom-lib"
version = "0.1.0"
source = "git"
git = "https://github.com/user/custom-lib"
commit = "abc123def456789..."
subdir = "packages/lib"  # Optional: subdirectory

Local Path

[[package]]
name = "local-lib"
version = "0.0.1"
source = "local"
path = "../local-lib"

Version Control

Always Commit hx.lock

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

.gitignore

Do NOT add hx.lock to .gitignore. It should be committed.

Merge Conflicts

If hx.lock has merge conflicts:

# Accept either version, then regenerate
git checkout --ours hx.lock  # or --theirs
hx lock
git add hx.lock
git commit

Commands

Generate/Update Lockfile

hx lock

Update Dependencies

hx lock --update

Verify Lockfile

hx lock --check

Install from Lockfile

hx sync

Relationship to .cabal

FilePurpose
*.cabalDeclares dependencies with version ranges
hx.lockPins exact versions that satisfy those ranges

Example:

# my-project.cabal
build-depends:
  , base ^>=4.18
  , aeson ^>=2.1
  , text >=2.0 && <3
# hx.lock (resolved)
[[package]]
name = "aeson"
version = "2.1.2.1"

[[package]]
name = "text"
version = "2.0.2"

Lockfile Strategies

Configure in hx.toml:

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

hx (Default)

Uses hx’s native lockfile format (TOML).

cabal-freeze

Uses cabal.project.freeze format for compatibility with plain Cabal projects.

Best Practices

1. Always Commit the Lockfile

Ensures reproducible builds for everyone.

2. Update Intentionally

# Check what's outdated
hx outdated

# Update specific packages
hx update aeson

# Regenerate lockfile
hx lock

3. Verify in CI

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

4. Review Lockfile Changes

When reviewing PRs, check hx.lock diffs for unexpected changes.

See Also