Make has been around for a long time. It does, however, have a moderate learning curve. Once you understand and overcome some of its idiosyncrasies, it is a very powerful generic build tool. Below are my variants for building Haskell projects using make. Most of my projects now use Stack. Even so, having a Makefile helps to coordinate building project artefacts. It becomes an organising script for the project.
A full description on building modules can be found in the chapter Using Make in the GHC documentation. For a single target, you can use make to build a Haskell project.
Make is whitespace sensitive. You must use tabs. The default is to use a tab setting of 8 spaces.
GHC
Below is a version using just GHC for compilation. We are defining a custom pattern to build from Haskell source (.hs) to an object (.o). This rule is cheating, as the end result is actually a executable, but as I didn't want to add a .exe extension, we are 'faking' it with testing just the object.
The build is dependent upon three other targets:
- build tags for use in editors like Vim and Emacs
- style to ensure consistent source code formatting
- lint to check code for bad code smells
- Makefile
#!/usr/bin/env make .SUFFIXES: .SUFFIXES: .o .hs %:%.hs -ghc -Wall -O2 -threaded --make $< .DEFAULT: build SRCS := $(wildcard *.hs) TGTS := $(patsubst %.hs, %, $(SRCS)) .PHONY: build build: check $(TGTS) .PHONY: check check: tags style lint .PHONY: all all: cleanall build style: $(SRCS) -stylish-haskell --config=.stylish-haskell.yaml --inplace $(SRCS) lint: $(SRCS) -hlint --color --show $(SRCS) tags: $(SRCS) -hasktags --ctags $(SRCS) .PHONY: clean clean: # replaces hs with o / hi in list of sources -$(RM) $(patsubst %.hs, %.o, $(SRCS)) -$(RM) $(patsubst %.hs, %.hi, $(SRCS)) .PHONY: cleanall cleanall: clean -$(RM) $(TGTS)
Cabal
Below we are using Cabal for compilation. This is essentially the same as for the build for GHC, but Cabal includes targets for checking, unit testing, performance benchmarks, documentation and execution. There is some setup involved to do all this, but that is described in the Cabal documentation. Cabal is useful if your project is using packages beyond the Prelude.
- Makefile
#!/usr/bin/env make TARGET := example SUBS := $(wildcard */) SRCS := $(wildcard $(addsuffix *.hs, $(SUBS))) ARGS ?= -h build: check @cabal build .PHONY: all all: check build test bench doc tags .PHONY: style style: @stylish-haskell -c .stylish-haskell.yaml -i $(SRCS) .PHONY: lint lint: @hlint --color $(SRCS) .PHONY: check check: lint style @cabal check .PHONY: exec exec: # Example: make ARGS="112 12" exec @cabal exec $(TARGET) -- $(ARGS) .PHONY: test test: @cabal test .PHONY: bench bench: @cabal bench .PHONY: tags tags: -hasktags --ctags $(SRCS) .PHONY: doc doc: @cabal haddock .PHONY: ghci ghci: -ghci -Wno-type-defaults .PHONY: clean clean: @cabal clean
Stack
Stack is a more complete project management tool, built on top of Cabal. Where Cabal was used in the Makefile previously, it has now been replaced by calls to Stack:
- Makefile
#!/usr/bin/env make TARGET := example SUBS := $(wildcard */) SRCS := $(wildcard $(addsuffix *.hs, $(SUBS))) ARGS ?= -h build: check @stack build .PHONY: all all: check build test bench doc tags .PHONY: style style: @stylish-haskell -c .stylish-haskell.yaml -i $(SRCS) .PHONY: lint lint: @hlint --color $(SRCS) .PHONY: check check: lint style .PHONY: exec exec: # Example: make ARGS="-h" exec @stack exec $(TARGET) -- $(ARGS) .PHONY: test test: @stack test .PHONY: bench bench: @stack bench .PHONY: tags tags: @hasktags --ctags $(SRCS) .PHONY: install install: @stack install .PHONY: doc doc: @stack haddock .PHONY: ghci ghci: @stack ghci --ghci-options -Wno-type-defaults .PHONY: clean clean: @stack clean .PHONY: cleanall cleanall: clean @$(RM) -rf .stack-work/
Other Examples
I hope these examples are a useful starting point. They could be extended. For example, by including a setup task. This would bootstrap a project, ensuring dependencies are met. Other tasks would be to package or deploy a target.