Each component should have a Makefile
to describe how to build, test and sometimes run the program. The Makefile
should provide the following information:
- Required programs
- Important build files
- Build (or run if not applicable) command
- Test command
- Cleanup operations
This is so that any developer can—after installing dependencies—run, build and test the module, without consulting other documentation or the README file. This also means that the README file should not contain such information—it should all be only found in the Makefile.
Example
See backend-core for now.
Sections
We'll divide the Makefile
into a few sections
Header
This is a simple header to refer back to this document. It should be the following lines, and end with two blank lines.
# See https://git.makerforce.io/beep/best-practices/wiki/Makefile
Definitions
This is where we define common variables. Each section should be seperated by a blank line, and preferrably not contain blank lines.
Programs
Any programs used by the Makefile
MUST be defined here. The side effect of defining them in this section also serves as a list of all the dependencies required to run, test or build the program. This makes it reasonable to assume that if a program is used here, one can simply install the program and run make
. Otherwise, comments should be made on what hidden dependencies exist, within reason. An example of such a hidden dependency could be a program that go generate
might call.
#
# Programs
#
GOCMD?=go
...
Local
It is common to need variables to describe constant attributes of the local build. For example, the output filename or folder. These should be declared in this section.
#
# Local
#
BINARY_NAME=core
Files
The Makefile
might need to reference special files used in the build, or maybe a entrypoint to be compiled. This can be defined in this section. A common example would be integration test docker-compose
files.
#
# Files
#
DOCKERCOMPOSE_INTEGRATION_CONFIG?=docker-compose.integration.yml
Tasks
This section is where we define all the tasks. It should start with the following header, surrounded by two blank lines.
#
# Tasks
#
all
The default task should be a quick way to test and compile the program. This might involve a unit test and build subtasks.
# Let's do a quick unit test and then build backend-core
all: test_fmt test_unit build
build
Should not mutate any code or do any tests.
build:
$(GOBUILD) -o $(BINARY_NAME) -v
deps
If the build requires dependencies that can be installed purely using the toolchain, you can call it from here. Make sure it does not affect the host system.
deps:
$(FLUTTER) pub get
test
This task should perform all tests that can be done, including integration and UI testing.
test: test_fmt test_unit test_integration
test_fmt
This is neat, it should test that the code follows convention formatting.
test_fmt:
$(GOFMT_PROG) -l .
test_unit
Don't depend on any running process.
test_unit:
$(GOTEST) -tags=unit -v -cover
test_integration
This can spawn docker-compose
setups using test_integration_prepare
followed by performing the test.
test_integration: test_integration_prepare
$(GOTEST) -tags=integration -v -cover
test_integration_prepare
, test_integration_cleanup
Note that the cleanup step must be manually called to clean up. This enables us to reuse the database. Tests should try their best to clean up after themselves.
This also means that the preparation step should only create one instance of any services, and the cleanup step must be manually run to shut that service down. A good example is show below. It checks for any running instances (including non-docker instances) and brings up the services required if it's not running.
test_integration_prepare:
$(GORUN) scripts/testutils.go isrunning || $(DOCKERCOMPOSE) -f $(DOCKERCOMPOSE_INTEGRATION_CONFIG) up -d
$(GORUN) scripts/testutils.go wait
test_integration_cleanup:
$(DOCKERCOMPOSE) -f $(DOCKERCOMPOSE_INTEGRATION_CONFIG) down
clean
Note that this is not a cleanup and should not call cleanup. It should be in charge of removing any compiled assets or build cache.
clean:
$(GOCLEAN)
rm -f $(BINARY_NAME)