Infrastructure-as-Code for your Haskell Workspaces.
HWM is a build-tool orchestration layer for Haskell workspaces. It connects tools you already rely on (cabal, stack, nix, hls) behind a single declarative config, with some current capability gaps documented below.
Think of HWM as Terraform for your local Haskell repository. Whether you are a Nix purist, a Stack loyalist, or rely purely on Cabal, HWM ensures the state of your project files matches your declared intent across all environments.
HWM is an active workspace maintainer that provides:
- The Universal Translator: Write one
hwm.yaml. HWM automatically derives and generatescabal.project,stack.yaml,hie.yaml,flake.nix, and.cabalfiles. - Zero Lock-in: HWM materializes standard configuration files directly at your project root. You can uninstall HWM at any time, and your repository will still build perfectly using standard native tools.
- Smart Bounds Synchronization: Maintain a beautifully aligned, single-source-of-truth dependency registry. HWM automatically injects these bounds across your entire monorepo.
- IDE Config Generation: HWM generates
hie.yamlat the project root unless ignored by target policy, selecting stack or cabal cradle shape based on the active environment builder. - Flexible sync target policy: Control sync behavior per target via
environments.targetsdefaults and per-profiletargetsoverrides. - Cabal-first source inclusion sync (cabal-only): HWM discovers modules from source dirs and updates
.cabalmodule inclusion only when out of sync.
HWM sits one layer above your toolchain. It separates your workspace intent from your build implementation.
graph TD
HWM[hwm.yaml] ===>|Single Source of Truth| Engine((HWM Engine))
subgraph "Native Configurations (Managed & Idempotent)"
Engine --> Cabal[cabal.project & *.cabal]
Engine --> Nix[flake.nix]
Engine --> Stack[stack.yaml]
Engine --> HLS[hie.yaml]
end
subgraph "Your Build Tools"
Cabal -.-> RunCabal[cabal build]
Nix -.-> RunNix[nix develop]
Stack -.-> RunStack[stack build]
end
style HWM fill:#f9f,stroke:#333,stroke-width:4px
cabal install hwmConvert any existing repository into an HWM workspace in seconds.
# 1. Generate hwm.yaml. HWM automatically discovers packages and infers dependencies.
hwm init
# 2. Sync configuration (Generates cabal.project, stack.yaml, flake.nix, hie.yaml)
hwm sync
# 3. View the visual dashboard of your workspace
hwm status
- Unknown command handling:
hwm <token>will execute a script only if<token>exists inscripts; otherwise it returns a command-oriented CLI error.hwm run <script>remains the explicit script path. - Script execution model:
hwm runexecutes scripts via the platform shell ($SHELL -con Unix-like systems,COMSPEC /con Windows) and appends extra args to the script command using shell-safe quoting. - IDE generation behavior: generated
hie.yamlfollows the active builder (stackcradle for stack environments,cabalcradle for cabal/nix/nix-cabal environments). - Generated files are ephemeral:
hwm synckeeps generated files aligned with the active environment and builder.- Builder-driven defaults:
cabal->cabal.projectstack->stack.yamlnix->flake.nixnix/cabal->cabal.project+flake.nix
hie.yamlis generated by default; you can override via target policy.- Optional
environments.targetsdefaults (and per-profiletargetsoverrides) can adjust sync modes per target (sync,check,ignore). packages: syncupdates dependency bounds and cabal source inclusion for cabal-only packages.- Cabal source inclusion sync discovers
.hsmodules from component source dirs (library, exe, test, bench, foreign-lib, sublibs), preserves generatedPaths_*, and skips rewrites when already in sync. - Packages with
package.yamlremain on hpack-driven behavior (no direct cabal source inclusion rewrite). checkmode is no-write:cabal/stack/nixtargets check presence,hievalidates builder/component compatibility and reports errors when invalid, andpackagesruns validation-only checks (including source inclusion drift reporting for cabal packages).
- Builder-driven defaults:
- Nix install gap (current):
hwm installwithnix/nix/cabalis not supported and fails with explicit errors. - Publish model:
hwm release publishis intentionally Cabalsdistbased (builder-independent, Hackage-oriented). - Artifacts environment policy:
release.artifacts[*].environmentsis an allowlist. Omitted means all environments are allowed,[]means none are allowed, non-empty list means only listed environments are allowed. - Environment removal safety: removing the last environment is blocked; removing the current default requires
hwm environments remove <ENV> --set-default <NEW_DEFAULT>.
| Capability | cabal | stack | nix | nix/cabal |
|---|---|---|---|---|
hwm build / hwm test |
✅ | ✅ | ✅ | ✅ |
hwm install |
✅ | ✅ | ❌ | ❌ |
hwm release artifacts --builder=... |
✅ | ✅ | ❌ | ✅ |
hwm release publish (Hackage) |
✅* | ✅* | ✅* | ✅* |
* hwm release publish is builder-independent by design (Cabal sdist + Hackage upload flow).
HWM uses a gorgeous, tabular dictionary to manage your dependencies. You define the bounds once in hwm.yaml, and HWM automatically injects them into every package in your monorepo.
registry:
Cabal: ">= 3.8 && <= 3.16.1.0"
aeson: ">= 1.5.6.0 && <= 2.2.3.0"
mtl: "> 2.0.0 && < 2.6.0"Audit & Fix: Audit your bounds against actual snapshots to ensure you only claim support for versions validated by your build matrix.
hwm registry audit --fix
Managing monorepos with dozens of packages is finally clean. HWM uses prefix grouping to elegantly decouple your internal structure from your globally unique Hackage package names.
# 1) Create a workspace group
hwm workspace add libs
# 2) Scaffold a package inside the group
hwm workspace add libs/coreworkspace:
libs:
prefix: morpheus-graphql
members:
- core
- client
- serverWhen HWM generates your configs, it automatically builds the exact relative paths (morpheus-graphql-core), saving you from writing out bloated package names over and over.
Bring the power of CI matrices directly into your local workspace. HWM allows you to define logical environments that map to specific GHC versions, toolchain toggles, and specific builders (stack, cabal, or nix).
Instead of writing complex bash routing in GitHub Actions or juggling multiple config files locally, you define your targets exactly once. HWM treats these files (stack.yaml, cabal.project, flake.nix, hie.yaml) as ephemeral generators—artifacts of your current profile.
environments:
# Global defaults for the workspace
builder: stack
default: stable
# Optional sync target policy (global fallback)
targets:
cabal: sync
stack: sync
nix: sync
hie: sync
packages: sync
profiles:
legacy:
ghc: 8.10.7
targets:
hie: ignore
stack:
extra-deps:
base-orphans: 0.8.1
fastsum: 0.1.0.0
stable:
ghc: 9.6.3
builder: nix/cabal # Uses Nix to provide the environment and Cabal to build
# Purpose-built CI profiles
ci-windows:
ghc: 9.6.3
builder: cabal
targets:
nix: ignore # Explicitly disable flake generation for this profile
packages: check
ci-nix:
ghc: 9.6.3
builder: nix
ci-mixed:
ghc: 9.6.3
builder: nix/cabal # Uses Nix to provide the environment and Cabal to buildSeamless CI Integration:
By defining profiles like ci-nix and ci-windows, your GitHub Actions workflow becomes incredibly simple. You just tell HWM to sync the environment, and it instantly pivots the workspace to use the correct underlying toolchain.
hwm sync ci-mixed
hwm build # Executes via 'nix develop --command cabal build'
# On Ubuntu/macOS runners:
hwm sync ci-nix
hwm build # Executes 'nix build --no-link .#env-ciNix-all' where 'env-ciNix-all' is a synthetic package that depends on all packages in the workspace, for environment 'ci-nix' with ghc 9.6.3.
# On Windows runners:
hwm sync ci-windows
hwm build # Executes via 'cabal build'Run Your Matrix Locally: Avoid "CI Ping-Pong" by running tests across all defined environments locally. HWM handles the context switching between builders (Stack vs. Cabal vs. Nix) automatically.
# Runs the test suite across every defined profile
hwm test --env=allManual Environment & IDE Switching:
When you run hwm sync, HWM updates build files and validates hie.yaml; it rewrites hie.yaml when invalid/missing unless targets.hie: ignore is set.
# Instantly aligns generated config files for GHC 8.10
hwm sync legacy
# Removing default env requires migration
hwm environments remove stable --set-default legacyYou can control sync behavior via targets defaults and per-profile overrides:
environments:
targets:
stack: sync
nix: sync
hie: ignore
packages: check
profiles:
ci:
ghc: 9.6.3
targets:
stack: ignoreHWM includes a lightweight, pass-through task runner. Define simple aliases for your most common workflows directly in hwm.yaml.
scripts:
format: sh scripts/format.sh
lint: hlint .
test: hwm test --fast
Argument forwarding note:
hwm run <script> [ARGS...] appends extra args directly to the script command. Prefer hwm run <script> -- [ARGS...] when forwarding flags.
HWM introduces Release Trains, a high-integrity system for decoupling workspace structure from distribution strategy while ensuring topological correctness.
Transform raw binaries into hashed, compressed distribution units using your preferred engine. HWM ensures every artifact is strictly validated before the publication phase begins.
NOTE:
hwm installalways uses the current/default environment (no--envoption).hwm installwithnix/nix/cabalis currently unsupported and fails with an explicit error.hwm release publishis intentionally Cabalsdistbased for Hackage publishing (builder-independent).hwm release artifacts --builder=nixis currently unsupported.- default output dir
.hwm/distis cleaned before artifact generation; custom--output-diris created if missing and not cleaned. release.artifacts[*].environmentsis an allowlist for artifact command execution: omitted = all envs,[]= no envs, non-empty list = only listed envs.- for Nix static release derivation generation, only explicitly listed environments are included.
environments:
builder: stack # or nix or cabal
release:
artifacts:
hwm: libs/_root_:hwm
Define groups of packages to be published to Hackage. HWM enforces a topological sort, ensuring "core" dependencies are published before the packages that rely on them.
release:
publish:
main:
- libs
Usage:
# Bump version across the workspace
hwm version minor
# Build local binaries and hashes with builder of choice
hwm release artifacts --builder=cabal
# Push a train to Hackage (Requires HACKAGE_AUTH_TOKEN in environment with a valid API token)
hwm release publish main| Feature | Standard Setup | Nix / Bazel | 🚀 HWM v0.2.0 |
|---|---|---|---|
| Config Source | Decentralized | Centralized | Centralized (hwm.yaml) |
| Build System Support | Single Tool | High Friction | ✅ Agnostic (Nix, Cabal, Stack) |
| Idempotency | Manual Edits | Varies | ✅ Silent Writes (mtime-safe) |
| IDE Setup | Manual hie.yaml |
Complex | ✅ Auto-Generated (Smart) |
| Lock-in | High | Extreme | ✅ Zero Lock-in |
HWM is currently in v0.2.0 (Beta). It was built to solve the orchestration needs of the Morpheus GraphQL ecosystem, where it successfully synchronizes 15+ packages across legacy and modern GHC profiles.
Your feedback is highly valued! Please open an issue if you encounter bugs or want to share how you are using HWM.





