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 = 1generated
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 dependenciesPackage 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: subdirectoryLocal 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 commitCommands
Generate/Update Lockfile
hx lockUpdate Dependencies
hx lock --updateVerify Lockfile
hx lock --checkInstall from Lockfile
hx syncRelationship to .cabal
| File | Purpose |
|---|---|
*.cabal | Declares dependencies with version ranges |
hx.lock | Pins 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 lock3. Verify in CI
- name: Verify lockfile
run: hx lock --check4. Review Lockfile Changes
When reviewing PRs, check hx.lock diffs for unexpected changes.
See Also
- hx lock — Generate lockfile
- hx sync — Install from lockfile
- hx outdated — Check for updates