Nowadays there are myriad build automation tools. At GoodCode we prefer
make, the tool that first appeared way back in '67, and is nowadays available on every operating system. There are a few popular variants (GNU make, BSD make), but for the simple use cases like the ones we're going to explore here, they all behave the same.
A make primer
Make is great at specifying inter-dependent tasks. Each task has a target (either a file that should be produced, or just a task name that can be referenced elsewhere), list of dependencies (what files or other tasks need to be done before it can start), and a series of commands. We can also define variables that we can use throughout the code.
The tasks are defined in one or more
Makefiles. The first defined task is the one that will be started by default (unless we manually specify the task we want). Here's an example that invokes the SASS compiler to produce the CSS for the project:
static/css/style.css: static/scss/style.scss sassc --style 'compressed' $< > $@
Here we've defined a single target, file
static/css/style.css that will be the result of the task. The task depends on
static/scss/style.scss, and the command to execute is the
sassc tool passing it in the source (dependency, that's the
$<), and storing the result to the desired location (that's the
$@ referencing the target name).
Also note that
Makefile, being an artefact from the bygone era, is picky about spacing. The rules must start with a tab, not spaces!
We can start the target with:
The automatic dependency tracking is at work here:
make will only rebuild
style.css if the corresponding dependency
Let's spice things up a bit:
SASSC=sassc SASS_OPTS=--style 'compressed' STYLES=static/scss/style.scss static/scss/base.scss static/scss/variables.scss ... static/scss/components.scss .PHONY: all clean all: static/css/style.css static/css/style.css: $(STYLES) $(SASSC) $(STYLE_OPTS) $(STYLES) > $@ clean: rm -f static/css/style.css
Here we turned the SASS compiler name and options into variables so we can easily change them or override from the command line. We've also dealing with a more realistic scenario where the CSS depends on more than just one file. We also added two new targets,
all (which does everything for a complete build - in this case, just ensures the CSS file is built) and
clean that cleans up the directory. As these two don't represent files in the file system (but are virtual names), we've indicated that to make using
Making a Django deployment
Armed with this knowledge, let's automate some tedious Django deployment tasks. Here's a typical Makefile we use at GoodCode, slightly simplified:
MANAGE=python manage.py TEST_SETTINGS=project.settings.test .PHONY: all help deps static migrate restart update deploy all: help help: @echo "Usage:" @echo " make update - update application" @echo " make deploy - pull and deploy the update" @echo " make test - run automated tests" staticfiles/css/style.css: sassc --style 'compressed' staticfiles/sass/style.scss > $@ deps: pip install -r requirements.txt static: $(MAKE) staticfiles/css/style.css $(MANAGE) collectstatic --noinput migrate: $(MANAGE) migrate restart: sudo restart app update: migrate static deploy: git pull --ff-only $(MAKE) deps $(MAKE) update $(MAKE) restart test: $(MANAGE) test $(TEST_SETTINGS)
Here we define a few low-level tasks: updating dependencies, compiling the SASS files, collecting the static files, running the migrations, restarting the server. These are things that we usually don't need to run manually, but part as more complex tasks (but we can run them manually if we want to).
We also define the higher-level tasks: updating the application and deploying it (which combines the update with pulling from the remote git repository and restarting the server instead). Running the tests is not a complex task, but if we need to pass on test-specific arguments (like the test settings) it's easier to put it in a Makefile than to copy-paste the test command from README.
$(MAKE) ... invocations are a way to run the specified task at the exact order we want. So with deploy, we first want to make a git pull (which is not a separate task itself), then deps, and only then update. To do this, we rerun make itself (the
$(MAKE) variable is defined by default) with the specified target.
Finally, a note that our restart task makes an assumption on how the app can be restarted. The exact details will vary from system to system, and based on how you've configured your server.
Benefits of make
A great thing about using