Guide to creating and publishing a Haskell library with hx.
Create the Project
hx new my-lib --lib
cd my-libThis creates:
my-lib/
├── src/
│ └── MyLib.hs
├── test/
│ └── Spec.hs
├── my-lib.cabal
├── hx.toml
├── CHANGELOG.md
└── README.mdLibrary Structure
Module Organization
src/
├── MyLib.hs # Main module (re-exports)
└── MyLib/
├── Core.hs # Core functionality
├── Parser.hs # Parsing utilities
├── Types.hs # Type definitions
└── Internal.hs # Internal (not exported)Main Module
-- src/MyLib.hs
module MyLib
( -- * Types
Config(..)
, Result(..)
-- * Core Functions
, process
, validate
-- * Utilities
, helper
) where
import MyLib.Core
import MyLib.TypesInternal Modules
-- src/MyLib/Internal.hs
module MyLib.Internal where
-- Not exported from main module
-- Used by other modules internallyCabal Configuration
Basic Setup
cabal-version: 3.0
name: my-lib
version: 0.1.0.0
license: MIT
license-file: LICENSE
author: Your Name
maintainer: you@example.com
synopsis: A useful library for doing things
description:
A longer description of what your library does.
Can span multiple lines.
category: Development
extra-doc-files: CHANGELOG.md
source-repository head
type: git
location: https://github.com/user/my-lib
common warnings
ghc-options: -Wall -Wcompat
library
import: warnings
exposed-modules:
MyLib
MyLib.Core
MyLib.Types
other-modules:
MyLib.Internal
hs-source-dirs: src
build-depends:
, base ^>=4.18
, text ^>=2.0
, containers ^>=0.6
default-language: GHC2021
default-extensions:
OverloadedStrings
DeriveGeneric
test-suite my-lib-test
import: warnings
type: exitcode-stdio-1.0
main-is: Spec.hs
other-modules:
MyLib.CoreSpec
MyLib.ParserSpec
hs-source-dirs: test
build-depends:
, base
, my-lib
, hspec ^>=2.11
, QuickCheck ^>=2.14
default-language: GHC2021Version Bounds
Use PVP-compatible bounds:
build-depends:
-- Allow patches, block minor
, text ^>=2.0
-- Allow minor, block major
, aeson >=2.0 && <3
-- Exact version (rarely needed)
, specific-package ==1.2.3Writing Documentation
Haddock Comments
-- | Process the input data.
--
-- Takes a 'Config' and produces a 'Result'.
--
-- ==== Examples
--
-- >>> process defaultConfig "input"
-- Right (Result "output")
--
-- >>> process badConfig ""
-- Left ConfigError
process :: Config -> Text -> Either Error Result
process = ...Module Documentation
{-|
Module : MyLib.Core
Description : Core functionality for MyLib
Copyright : (c) Your Name, 2024
License : MIT
Maintainer : you@example.com
Stability : experimental
This module provides the core 'process' function
and related utilities.
-}
module MyLib.Core whereGenerate Documentation
hx doc --openTesting
Test Structure
-- test/Spec.hs
{-# OPTIONS_GHC -F -pgmF hspec-discover #-}-- test/MyLib/CoreSpec.hs
module MyLib.CoreSpec where
import Test.Hspec
import Test.QuickCheck
import MyLib.Core
spec :: Spec
spec = do
describe "process" $ do
it "handles valid input" $ do
process defaultConfig "valid" `shouldBe` Right expected
it "rejects empty input" $ do
process defaultConfig "" `shouldSatisfy` isLeft
it "is idempotent" $ property $ \input ->
process cfg (process cfg input) == process cfg inputRun Tests
hx test
hx test --match "Core"
hx test -- --quickcheck-tests 1000Version Management
Semantic Versioning
Follow PVP (Package Versioning Policy):
A.B.C.D
│ │ │ └─ Patch: bug fixes
│ │ └─── Minor: backwards-compatible additions
│ └───── Major: breaking changes (type signatures)
└─────── Major: breaking changes (module structure)Updating Version
Update in
.cabal:version: 0.2.0.0Update CHANGELOG.md:
## 0.2.0.0 - 2024-01-15 ### Added - New `parseStrict` function ### Changed - `process` now returns `Either Error Result`
Publishing to Hackage
Prerequisites
- Create Hackage account
- Get upload credentials
Pre-Publish Checklist
# Verify everything builds
hx build --release
# Run all tests
hx test
# Check documentation
hx doc --open
# Lint the code
hx lint
# Check package
cabal checkCreate Source Distribution
cabal sdistCreates dist-newstyle/sdist/my-lib-0.1.0.0.tar.gz
Upload
# Candidate (for review)
cabal upload dist-newstyle/sdist/my-lib-0.1.0.0.tar.gz
# Final publish
cabal upload --publish dist-newstyle/sdist/my-lib-0.1.0.0.tar.gzBest Practices
1. Minimize Dependencies
Only add essential dependencies:
build-depends:
, base ^>=4.18
-- Only what you really need2. Export Thoughtfully
module MyLib
( -- Only export stable API
Config(..)
, process
-- Don't export: internalHelper
) where3. Use Internal Modules
exposed-modules: MyLib
other-modules: MyLib.Internal4. Provide Examples
-- | Example usage:
--
-- @
-- import MyLib
--
-- main = do
-- result <- process defaultConfig input
-- print result
-- @5. Maintain Changelog
Keep CHANGELOG.md updated with every release.