GNU Make provide a simple and consistent way to run any development task for Clojure & ClojureScript projects (or any other languages).
Wrap any combination of tools (building, linting, formatting, testing) with make targets for a simple command line interface, with automatically tab completion, making any Clojure project really easy to work with. Practicalli also uses make to manage docker images and containers to support Clojure development.
All that is required is a
Makefile in the root of the project
GNU Make overviewλ︎
GNU Make is a language agnostic build automation tool which has been an integral part of building Linux/Unix operating system code and applications for decades, providing a consistent way to configure, compile and deploy code for all projects.
Makefile defines targets called via the
make command. Each target can run one or more commands. Targets can be dependent on other targets, e.g the
dist target that builds a project can be dependent on
Practicalli also uses
maketo configure and build the latest versions of Emacs and other Linux open source software
Makefile in the root of a project and define a target by typing a suitable name followed by a
: character, e.g.
Insert a tab on the next line and type a command to be called. Further commands can be added on new lines so long as each line is tab indented.
Common target namingλ︎
Targets used across Practicalli projects follow the make standard targets for users
disttargets are recommended for use with a CI deployment pipeline and builder stage when using Docker.
allcalling all targets to prepare the application to be run. e.g. all: deps test-ci dist clean
depsdownload library dependencies (depend on
distcreate a distribution tar file for this program or zip deployment package for AWS Lambda
lintrun lint tools to check code quality - e.g MegaLinter which provides a wide range of tools
format-checkreport format and style issues for a specific programming language
format-fixupdate source code files if there are format and style issues for a specific programming language
pre-commitrun unit tests and code quality targets before considering a Git commit
replrun an interactive run-time environment for the programming language
test-unitrun all unit tests
test-citest running in CI build (optionally focus on integration testing)
cleanremove files created by any of the commands from other targets (i.e. ensure a clean build each time)
practicalli/dotfiles/Makefile also defines docker targets to build and compose images locally, inspect images and prune containers and images.
Makefile target can depend on either a file name or another target in the
The all target typically depends on several
Makefile targets to test, compile and package a service. Add the names of the targets this target depends upon
Add the filename of a file after the name of the target, to depend on if that file has changed. If the file has not changed since make was last run then the task will not run again.
Clojure CLI Example: If the
deps target depends on
deps.edn and the file has not changed since last run, the deps target will not run again.
deps target - depend on a fileλ︎
The deps target would use Clojure CLI or Leiningen to download dependencies.
deps target to depend on
project.clj file, then if the file has not changed the deps will not run again.
A Clojure CLI example depends on the
deps.edn file that defines all the library dependencies for the project, tools for testing and packaging the Clojure service. The
-P flag is the prepare option, a dry run that only downloads the dependencies for the given tasks.
:test/envadds libraries to run Kaocha and libraries used to run unit tests.
:package/uberjarruns a tool that creates an uberjar.
Clean target - hiding command failureλ︎
The clean target should remove files and directories created by the build (compile) process, to ensure a consistent approach to building each time.
On Linux / Unix systems files can be deleted with the
rm command using
-r for recursively deleting a directory and its contents.
-f forces the deleting of files and directories, otherwise a prompt for confirmation of the delete may be shown.
- before a command instructs
make to ignore an error code, useful if the files to be deleted did not exist (i.e. the build failed part way through and not all files were created).
MegaLinter target - simplifying a commandλ︎
lint target is an example of how the
Makefile simplifies the command line interface.
lint: target is used to call the MegaLinter runner, avoiding the need to remember the common options passed when calling MegaLinter command line tool,
The Java flavor of MegaLinter is less than 2Gb image (full MegaLinter image is 8Gb) and contains all the tools for a Clojure project. The flavor can only be set via a command line option, so the make file ensures that option is always used and the full MegaLinter docker image is not downloaded by mistake.
When MegaLinter is configured to generate reports (default),
lint-clean: target is used to clean those reports.
Enhancing make outputλ︎
info message is used with each target to enhances the readability of the make output, especially when multiple targets and commands are involved, or if commands are generating excessive output to standard out.
Avoiding file name collisionsλ︎
Although unlikely, if a filename in the root of a project has the same name as a
Makefile target, it can be used instead of running the targets command
.PHONY: defines the names of targets in the
Makefile to avoid name clashes
Halt on failureλ︎
.DELETE_ON_ERROR: halts any further commands if a command returns non-zero exit status. Useful as short-circuit to stop tasks when further work is not valuable, e.g. if tests fail then it may not be valuable to build the Clojure project.
Makefile can simplify the command line interface for any task with a Clojure project (or any other language and tooling).
Using the same target names across all projects reduces the cognitive load for driving any project.